แนวทางปฏิบัติที่ดีที่สุดในการเขียนสคริปต์ Bash: คู่มือฉบับย่อสำหรับสคริปต์ Bash ที่เชื่อถือได้และมีประสิทธิภาพ

แนวทางปฏิบัติที่ดีที่สุดในการเขียนสคริปต์ Bash: คู่มือฉบับย่อสำหรับสคริปต์ Bash ที่เชื่อถือได้และมีประสิทธิภาพ
วอลล์เปเปอร์เชลล์โดย manapi

การดีบักสคริปต์ทุบตีก็เหมือนกับการมองหาเข็มในกองหญ้า โดยเฉพาะอย่างยิ่งเมื่อมีการเพิ่มเติมใหม่ปรากฏในโค้ดเบสที่มีอยู่โดยไม่ต้องคำนึงถึงปัญหาด้านโครงสร้าง การบันทึก และความน่าเชื่อถืออย่างทันท่วงที คุณสามารถพบว่าตัวเองอยู่ในสถานการณ์ดังกล่าวเนื่องจากความผิดพลาดของคุณเองหรือเมื่อจัดการกองสคริปต์ที่ซับซ้อน

ทีม Mail.ru โซลูชั่นคลาวด์ แปลบทความพร้อมคำแนะนำที่จะช่วยให้คุณเขียน แก้ไขข้อบกพร่อง และบำรุงรักษาสคริปต์ของคุณได้ดีขึ้น เชื่อหรือไม่ ไม่มีอะไรจะดีไปกว่าความพึงพอใจในการเขียนโค้ด bash ที่สะอาดและพร้อมใช้งานซึ่งใช้งานได้ทุกครั้ง

ในบทความ ผู้เขียนแบ่งปันสิ่งที่เขาได้เรียนรู้ในช่วงไม่กี่ปีที่ผ่านมา รวมถึงข้อผิดพลาดทั่วไปบางประการที่ทำให้เขาไม่ทันระวัง นี่เป็นสิ่งสำคัญเนื่องจากนักพัฒนาซอฟต์แวร์ทุกคนจะทำงานร่วมกับสคริปต์เพื่อทำให้งานประจำเป็นไปโดยอัตโนมัติ ณ จุดใดจุดหนึ่งในอาชีพของตน

ตัวจัดการกับดัก

สคริปต์ทุบตีส่วนใหญ่ที่ฉันเคยพบไม่เคยใช้กลไกการล้างข้อมูลที่มีประสิทธิภาพเมื่อมีสิ่งที่ไม่คาดคิดเกิดขึ้นระหว่างการเรียกใช้สคริปต์

ความประหลาดใจอาจเกิดขึ้นได้จากภายนอก เช่น การรับสัญญาณจากแกนกลาง การจัดการกรณีดังกล่าวเป็นสิ่งสำคัญอย่างยิ่งเพื่อให้แน่ใจว่าสคริปต์มีความน่าเชื่อถือเพียงพอที่จะทำงานบนระบบที่ใช้งานจริง ฉันมักจะใช้ตัวจัดการทางออกเพื่อตอบสนองต่อสถานการณ์เช่นนี้:

function handle_exit() {
  // Add cleanup code here
  // for eg. rm -f "/tmp/${lock_file}.lock"
  // exit with an appropriate status code
}
  
// trap <HANDLER_FXN> <LIST OF SIGNALS TO TRAP>
trap handle_exit 0 SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM

trap เป็นคำสั่งเชลล์ในตัวที่ช่วยให้คุณลงทะเบียนฟังก์ชันการล้างข้อมูลที่ถูกเรียกในกรณีที่มีสัญญาณใดๆ อย่างไรก็ตาม ควรใช้ความระมัดระวังเป็นพิเศษกับผู้ปฏิบัติงาน เช่น SIGINTซึ่งทำให้สคริปต์ถูกยกเลิก

นอกจากนี้ในกรณีส่วนใหญ่คุณควรจับเท่านั้น EXITแต่แนวคิดก็คือคุณสามารถปรับแต่งพฤติกรรมของสคริปต์สำหรับสัญญาณแต่ละรายการได้

ฟังก์ชั่นการตั้งค่าในตัว - การยกเลิกอย่างรวดเร็วเมื่อมีข้อผิดพลาด

เป็นสิ่งสำคัญมากที่จะต้องตอบสนองต่อข้อผิดพลาดทันทีที่เกิดขึ้นและหยุดการดำเนินการอย่างรวดเร็ว ไม่มีอะไรจะแย่ไปกว่าการรันคำสั่งแบบนี้ต่อไป:

rm -rf ${directory_name}/*

โปรดทราบว่าตัวแปร directory_name ไม่ได้กำหนด

การใช้ฟังก์ชันในตัวเพื่อจัดการกับสถานการณ์ดังกล่าวเป็นสิ่งสำคัญ setเช่น set -o errexit, set -o pipefail หรือ set -o nounset ที่จุดเริ่มต้นของสคริปต์ ฟังก์ชันเหล่านี้ช่วยให้แน่ใจว่าสคริปต์ของคุณจะออกทันทีที่พบโค้ดออกที่ไม่ใช่ศูนย์ การใช้ตัวแปรที่ไม่ได้กำหนด คำสั่งที่ไม่ถูกต้องที่ส่งผ่านไปป์ และอื่นๆ:

#!/usr/bin/env bash

set -o errexit
set -o nounset
set -o pipefail

function print_var() {
  echo "${var_value}"
}

print_var

$ ./sample.sh
./sample.sh: line 8: var_value: unbound variable

หมายเหตุ: ฟังก์ชั่นในตัวเช่น set -o errexitจะออกจากสคริปต์ทันทีที่มีโค้ดส่งคืน "ดิบ" (นอกเหนือจากศูนย์) ดังนั้นจึงควรแนะนำการจัดการข้อผิดพลาดแบบกำหนดเอง เช่น:

#!/bin/bash
error_exit() {
  line=$1
  shift 1
  echo "ERROR: non zero return code from line: $line -- $@"
  exit 1
}
a=0
let a++ || error_exit "$LINENO" "let operation returned non 0 code"
echo "you will never see me"
# run it, now we have useful debugging output
$ bash foo.sh
ERROR: non zero return code from line: 9 -- let operation returned non 0 code

การเขียนสคริปต์ด้วยวิธีนี้จะทำให้คุณต้องระมัดระวังมากขึ้นเกี่ยวกับพฤติกรรมของคำสั่งทั้งหมดในสคริปต์ และคาดการณ์ความเป็นไปได้ที่จะเกิดข้อผิดพลาดก่อนที่คุณจะทำให้คุณประหลาดใจ

ShellCheck เพื่อตรวจจับข้อผิดพลาดระหว่างการพัฒนา

มันคุ้มค่าที่จะบูรณาการบางอย่างเช่น เชลล์เช็ค ในการพัฒนาและทดสอบไปป์ไลน์ของคุณเพื่อตรวจสอบโค้ดทุบตีของคุณกับแนวปฏิบัติที่ดีที่สุด

ฉันใช้มันในสภาพแวดล้อมการพัฒนาในพื้นที่ของฉันเพื่อรับรายงานเกี่ยวกับไวยากรณ์ ความหมาย และข้อผิดพลาดบางอย่างในโค้ดที่ฉันอาจพลาดไปขณะพัฒนา นี่เป็นเครื่องมือวิเคราะห์แบบคงที่สำหรับสคริปต์ทุบตีของคุณ และฉันขอแนะนำให้ใช้มันเป็นอย่างยิ่ง

ใช้รหัสทางออกของคุณเอง

รหัสส่งคืนใน POSIX ไม่ใช่เพียงศูนย์หรือหนึ่ง แต่เป็นค่าศูนย์หรือค่าที่ไม่ใช่ศูนย์ ใช้คุณสมบัติเหล่านี้เพื่อส่งคืนรหัสข้อผิดพลาดแบบกำหนดเอง (ระหว่าง 201-254) สำหรับกรณีข้อผิดพลาดต่างๆ

ข้อมูลนี้สามารถนำมาใช้โดยสคริปต์อื่นๆ ที่ล้อมรอบข้อมูลของคุณเพื่อทำความเข้าใจว่าข้อผิดพลาดประเภทใดที่เกิดขึ้นและตอบสนองตามนั้น:

#!/usr/bin/env bash

SUCCESS=0
FILE_NOT_FOUND=240
DOWNLOAD_FAILED=241

function read_file() {
  if ${file_not_found}; then
    return ${FILE_NOT_FOUND}
  fi
}

หมายเหตุ: โปรดใช้ความระมัดระวังเป็นพิเศษกับชื่อตัวแปรที่คุณกำหนด เพื่อหลีกเลี่ยงการลบล้างตัวแปรสภาพแวดล้อมโดยไม่ตั้งใจ

ฟังก์ชั่นการบันทึก

การบันทึกที่สวยงามและมีโครงสร้างเป็นสิ่งสำคัญในการทำความเข้าใจผลลัพธ์ของสคริปต์ของคุณได้อย่างง่ายดาย เช่นเดียวกับภาษาการเขียนโปรแกรมระดับสูงอื่นๆ ฉันมักจะใช้ฟังก์ชันการบันทึกแบบเนทีฟในสคริปต์ทุบตีของฉัน เช่น __msg_info, __msg_error เป็นต้น

ซึ่งจะช่วยให้มีโครงสร้างการบันทึกที่เป็นมาตรฐานโดยทำการเปลี่ยนแปลงในที่เดียวเท่านั้น:

#!/usr/bin/env bash

function __msg_error() {
    [[ "${ERROR}" == "1" ]] && echo -e "[ERROR]: $*"
}

function __msg_debug() {
    [[ "${DEBUG}" == "1" ]] && echo -e "[DEBUG]: $*"
}

function __msg_info() {
    [[ "${INFO}" == "1" ]] && echo -e "[INFO]: $*"
}

__msg_error "File could not be found. Cannot proceed"

__msg_debug "Starting script execution with 276MB of available RAM"

ฉันมักจะพยายามมีกลไกบางอย่างในสคริปต์ของฉัน __initโดยที่ตัวแปรตัวบันทึกและตัวแปรระบบอื่นๆ ได้รับการเตรียมใช้งานหรือตั้งค่าเป็นค่าเริ่มต้น ตัวแปรเหล่านี้สามารถตั้งค่าได้จากตัวเลือกบรรทัดคำสั่งระหว่างการเรียกใช้สคริปต์

ตัวอย่างเช่น:

$ ./run-script.sh --debug

เมื่อเรียกใช้สคริปต์ดังกล่าว จะช่วยให้แน่ใจว่าการตั้งค่าทั้งระบบได้รับการตั้งค่าเป็นค่าเริ่มต้นหากจำเป็น หรืออย่างน้อยก็เตรียมใช้งานสิ่งที่เหมาะสมหากจำเป็น

ฉันมักจะเลือกว่าอะไรจะเริ่มต้นและอะไรไม่ควรทำในการแลกเปลี่ยนระหว่างอินเทอร์เฟซผู้ใช้กับรายละเอียดของการกำหนดค่าที่ผู้ใช้สามารถ/ควรเจาะลึก

สถาปัตยกรรมสำหรับการใช้ซ้ำและสถานะระบบที่สะอาด

รหัสแบบแยกส่วน/แบบใช้ซ้ำได้

├── framework
│   ├── common
│   │   ├── loggers.sh
│   │   ├── mail_reports.sh
│   │   └── slack_reports.sh
│   └── daily_database_operation.sh

ฉันเก็บพื้นที่เก็บข้อมูลแยกต่างหากไว้ซึ่งสามารถใช้เพื่อเริ่มต้นโปรเจ็กต์/สคริปต์ทุบตีใหม่ที่ฉันต้องการพัฒนา ทุกสิ่งที่สามารถนำมาใช้ซ้ำได้สามารถจัดเก็บไว้ในพื้นที่เก็บข้อมูลและเรียกคืนโดยโปรเจ็กต์อื่นที่ต้องการใช้ฟังก์ชันนั้น การจัดระเบียบโปรเจ็กต์ด้วยวิธีนี้จะช่วยลดขนาดของสคริปต์อื่นๆ ลงอย่างมาก และยังช่วยให้แน่ใจว่าฐานโค้ดมีขนาดเล็กและง่ายต่อการทดสอบ

ดังตัวอย่างข้างต้น ฟังก์ชันการบันทึกทั้งหมด เช่น __msg_info, __msg_error และอื่นๆ เช่น รายงาน Slack มีแยกไว้ใน common/* และเชื่อมต่อแบบไดนามิกในสถานการณ์อื่นๆ เช่น daily_database_operation.sh.

ทิ้งระบบที่สะอาดไว้เบื้องหลัง

หากคุณกำลังโหลดทรัพยากรใดๆ ในขณะที่สคริปต์กำลังทำงานอยู่ ขอแนะนำให้จัดเก็บข้อมูลดังกล่าวทั้งหมดไว้ในไดเร็กทอรีที่ใช้ร่วมกันโดยใช้ชื่อแบบสุ่ม เช่น /tmp/AlRhYbD97/*. คุณสามารถใช้ตัวสร้างข้อความแบบสุ่มเพื่อเลือกชื่อไดเร็กทอรี:

rand_dir_name="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"

หลังจากเสร็จสิ้นงาน สามารถจัดเตรียมการล้างข้อมูลไดเร็กทอรีดังกล่าวได้ในตัวจัดการฮุกที่กล่าวถึงข้างต้น หากไดเร็กทอรีชั่วคราวไม่ได้รับการดูแล ไดเร็กทอรีเหล่านั้นจะสะสมและในบางขั้นตอนทำให้เกิดปัญหาที่ไม่คาดคิดบนโฮสต์ เช่น ดิสก์เต็ม

การใช้ไฟล์ล็อค

บ่อยครั้งที่คุณต้องแน่ใจว่ามีสคริปต์เพียงอินสแตนซ์เดียวเท่านั้นที่ทำงานบนโฮสต์ในเวลาใดก็ตาม ซึ่งสามารถทำได้โดยใช้ไฟล์ล็อค

ฉันมักจะสร้างไฟล์ล็อคใน /tmp/project_name/*.lock และตรวจสอบการมีอยู่ของพวกเขาที่จุดเริ่มต้นของสคริปต์ ซึ่งจะช่วยให้สคริปต์สิ้นสุดอย่างสวยงามและหลีกเลี่ยงการเปลี่ยนแปลงสถานะระบบโดยไม่คาดคิดโดยสคริปต์อื่นที่ทำงานพร้อมกัน ไม่จำเป็นต้องใช้ไฟล์ล็อคหากคุณต้องการให้สคริปต์เดียวกันดำเนินการแบบขนานบนโฮสต์ที่กำหนด

วัดและปรับปรุง

เรามักจะต้องทำงานกับสคริปต์ที่ทำงานเป็นระยะเวลานาน เช่น การทำงานของฐานข้อมูลรายวัน โดยทั่วไปการดำเนินการดังกล่าวจะเกี่ยวข้องกับลำดับขั้นตอนต่างๆ เช่น การโหลดข้อมูล การตรวจสอบความผิดปกติ การนำเข้าข้อมูล การส่งรายงานสถานะ และอื่นๆ

ในกรณีเช่นนี้ ฉันมักจะพยายามแบ่งสคริปต์ออกเป็นสคริปต์เล็กๆ แยกกัน และรายงานสถานะและเวลาดำเนินการโดยใช้:

time source "${filepath}" "${args}">> "${LOG_DIR}/RUN_LOG" 2>&1

หลังจากนั้นฉันสามารถดูเวลาดำเนินการด้วย:

tac "${LOG_DIR}/RUN_LOG.txt" | grep -m1 "real"

ซึ่งช่วยฉันระบุปัญหา/ส่วนที่ช้าในสคริปต์ที่ต้องการการปรับให้เหมาะสม

โชคดี!

มีอะไรให้อ่านอีก:

  1. ไปและแคช GPU
  2. ตัวอย่างของแอปพลิเคชันที่ขับเคลื่อนด้วยเหตุการณ์ซึ่งอิงตาม webhooks ในพื้นที่จัดเก็บอ็อบเจ็กต์ S3 ของ Mail.ru Cloud Solutions
  3. ช่องโทรเลขของเราเกี่ยวกับการเปลี่ยนแปลงทางดิจิทัล

ที่มา: will.com

ซื้อโฮสติ้งที่เชื่อถือได้สำหรับไซต์ที่มีการป้องกัน DDoS เซิร์ฟเวอร์ VPS VDS 🔥 ซื้อบริการเว็บโฮสติ้งที่เชื่อถือได้ พร้อมระบบป้องกัน DDoS และเซิร์ฟเวอร์ VPS/VDS | ProHoster