Bash בסיסי
shebang ו-set -euo pipefail
Section titled “shebang ו-set -euo pipefail”כל script מתחיל בשתי שורות, ומשם ממשיכים לכתוב.
#!/usr/bin/env bashset -euo pipefail| דגל | משמעות | בלעדיו |
|---|---|---|
-e | ה-script נעצר בפקודה הראשונה שנכשלה (exit code שונה מ-0) | ה-script ממשיך אחרי כישלון |
-u | משתנה לא מוגדר הוא שגיאה | משתנה לא מוגדר הופך למחרוזת ריקה בשקט |
-o pipefail | pipeline נכשל אם כל שלב בו נכשל | נבחר רק ה-exit code של השלב האחרון |
set -o pipefail; false | true; echo "rc=$?" # rc=1 (without pipefail: rc=0)bash -c 'set -u; echo "$UNSET_VAR"' # bash: UNSET_VAR: unbound variableexit codes: 0 = הצלחה
Section titled “exit codes: 0 = הצלחה”כל פקודה מחזירה מספר. 0 אומר "הצליח" (או "אין מה לשנות"), כל מספר אחר אומר "נכשל" או "מצאתי הבדל". אחרי הפקודה, $? מחזיק את המספר.
git diff --cached --quiet; echo "rc=$?" # rc=0: nothing staged, no differenceecho x > f; git add fgit diff --cached --quiet; echo "rc=$?" # rc=1: there IS a staged changeif ! cmd ופטור set -e בתנאי
Section titled “if ! cmd ופטור set -e בתנאי”פקודה שנמצאת בתוך התנאי של if (או לפני && / ||) פטורה מ-set -e: כישלון שלה הוא תשובה, לא תקלה.
git add "$FILE"if ! git diff --cached --quiet; then # exit 1 = there is a change git commit -q -m "promote($ENV): $CANDIDATE" git push -q origin "HEAD:$BRANCH"figit rev-parse HEAD # outside the if: always runs# WRONG under set -e: a bare diff exits 1 exactly when there IS a change, so the script dies right thereset -egit diff --cached --quietgit commit -m "never reached when there is something to commit"&& ו-||
Section titled “&& ו-||”a && b מריץ את b רק אם a הצליחה. a || b מריץ את b רק אם a נכשלה.
command -v kubectl >/dev/null || { echo "kubectl not found" >&2; exit 1; }[ -s "$FILE" ] && echo "non-empty"מלכודת: a && b || c אינו if/else. אם b נכשלה, c רצה גם כש-a הצליחה. כשצריך שני ענפים, כתוב if … then … else … fi.
pipe ו-$(...)
Section titled “pipe ו-$(...)”| מעביר את הפלט של פקודה לקלט של הבאה. $(...) מחליף את עצמו בפלט של הפקודה.
kubectl create namespace "$NS" --dry-run=client -o yaml | kubectl apply -f -COUNT="$(kubectl get pods -n "$NS" --no-headers | wc -l)"הרעיון בשורה הראשונה: הפקודה השמאלית מייצרת YAML, הימנית מחילה אותו. ה--f - אומר "קרא מ-stdin".
${1:?msg} — ארגומנט חובה
Section titled “${1:?msg} — ארגומנט חובה”$1, $2 הם הארגומנטים של ה-script. ${1:?msg} עוצר עם msg אם הארגומנט חסר או ריק.
ENV="${1:?usage: promote.sh <ENV> <CANDIDATE>}"CANDIDATE="${2:?usage: promote.sh <ENV> <CANDIDATE>}"promote.sh: line 33: 2: usage: promote.sh <ENV> <CANDIDATE>ההודעה היא ה"usage" של ה-script, אז כתוב בה מה חסר. אותו דבר למשתנים מהסביבה: : "${GITOPS_REPO:?export GITOPS_REPO first}".
${VAR:-default} — ערך ברירת מחדל
Section titled “${VAR:-default} — ערך ברירת מחדל”אם VAR לא מוגדר או ריק, מקבלים את ה-default. כך מאפשרים override בלי לערוך את הקובץ.
DIR="${TRIDENT_LOCAL_DIR:-$HOME/.local/share/trident}"bash -c 'echo "${FOO:-dev}"' # dev:- מחליף רק בתוך הביטוי ולא מגדיר את המשתנה.
${!NAME} — משתנה לפי שם
Section titled “${!NAME} — משתנה לפי שם”כשהשם של המשתנה עצמו נמצא במשתנה אחר.
NAME="gitops"URL_VAR="TRIDENT_$(printf %s "$NAME" | tr a-z A-Z)_URL" # TRIDENT_GITOPS_URLREPO_URL="${!URL_VAR:?export $URL_VAR=<your repo URL>}"כשהמשתנה ריק ההודעה נראית כך, והיא מציגה את שם הפרמטר (!URL_VAR) ואחריו את הטקסט שלך:
line 22: !URL_VAR: export TRIDENT_GITOPS_URL=<your repo URL>מה שאחרי export בהודעה הוא המשתנה שחסר. ראה גם export מול CI variables.
מרכאות: "$VAR" תמיד
Section titled “מרכאות: "$VAR" תמיד”בלי מרכאות, bash מפצל את הערך על רווחים ומרחיב *. עם מרכאות כפולות הערך נשאר מחרוזת אחת.
FILE="my file.txt"ls $FILE # two arguments: "my" and "file.txt"ls "$FILE" # one argumentecho 'single quotes: $FILE stays literal'כלל: "..." כשיש משתנה בפנים, '...' כשרוצים טקסט מילולי (למשל jsonpath: -o 'jsonpath={.data.key}').
export, משתנה רגיל, ו-CI variable
Section titled “export, משתנה רגיל, ו-CI variable”שלושה מקומות שונים, שלושה כללים. השאלה הקובעת: איפה ה-script רץ, ומי מגדיר לו את המשתנים.
| ה-script רץ… | המשתנים מגיעים מ… |
|---|---|
ב-terminal ב-VM (bash bootstrap/x.sh) | export NAME=value באותו terminal (וצריך שוב בכל terminal חדש) |
| בתוך job של pipeline | GitLab CI/CD variables (gitlab/variables) |
| בתוך script אחר שהרצת | רק משתנים שעברו export לפני כן (משתנה רגיל לא עובר ל-child process) |
FOO=1; bash -c 'echo "plain: [${FOO:-empty}]"' # plain: [empty]export FOO; bash -c 'echo "exported: [${FOO:-empty}]"' # exported: [1]echo "[$TRIDENT_GITOPS_URL]"; env | grep TRIDENT_ # is it set in THIS shell?read -rs — קריאת token בלי להציג אותו
Section titled “read -rs — קריאת token בלי להציג אותו”-s שקט (לא מציג מה שמקלידים), -r raw (לא מפרש \ כתו בריחה).
read -rs TOKEN # type the value, press Enter; nothing is shownprintf '\n'echo 'a\b' | bash -c 'read T; echo "$T"' # ab (backslash eaten)echo 'a\b' | bash -c 'read -r T; echo "$T"' # a\bumask 077, chmod 600, printf '%s' — קובץ credential
Section titled “umask 077, chmod 600, printf '%s' — קובץ credential”שלושה דברים נכונים לקובץ שמחזיק token: הרשאות קובץ, הרשאות תיקייה, ותוכן בלי שורה חדשה.
umask 077 # new files 600, new dirs 700install -d -m 700 "$HOME/.local/share/x"printf '%s' "$TOKEN" > "$HOME/.local/share/x/token"chmod 600 "$HOME/.local/share/x/token" # in case the file already existed| ביטוי | תוצאה |
|---|---|
echo x > f | 2 בייטים: x + newline |
echo -n x > f | 1 בייט (אבל -n לא זהה בכל shell) |
printf '%s' x > f | 1 בייט, בכל shell |
install -d -m 700 a/b/c קובע 700 רק לתיקייה האחרונה: a ו-a/b נוצרות 755 אם לא הפעלת umask 077 ו-mkdir -p קודם. ה-script המלא: credential writer.
רווח אחרי \ בסוף שורה
Section titled “רווח אחרי \ בסוף שורה”\ בסוף שורה אומר "הפקודה ממשיכה". אם יש אחריו רווח, ה-\ מבריח את הרווח, והשורה נגמרת. השורה הבאה רצה כפקודה נפרדת.
printf 'echo a \\ \n b\n' > t.sh # a line ending with "\ " (backslash + space)cat -A t.sh # shows: echo a \ $ <- the space before the $grep -nE '\\ +$' t.sh # finds a backslash followed by spaces at end of linegrep -nE ' +$' t.sh # any trailing whitespacebash -n — בדיקת syntax בלי להריץ
Section titled “bash -n — בדיקת syntax בלי להריץ”מפרש את הקובץ ולא מריץ. תופס if בלי fi, מרכאה פתוחה, סוגריים. לא תופס שם משתנה שגוי או שם פקודה שגוי.
bash -n scripts/promote.sh && echo "syntax ok"bash -n scripts/*.sh # all of them; no output = finebad.sh: line 3: syntax error: unexpected end of filesed -i 's/\r$//' — תיקון CRLF
Section titled “sed -i 's/\r$//' — תיקון CRLF”קובץ שנוצר או הועתק מ-Windows מסיים שורות ב-\r\n, ו-bash קורא את ה-\r כחלק מהפקודה (שגיאות מוזרות על פקודה תקינה).
grep -c $'\r' scripts/example.sh # > 0 means CRLF is presentsed -i 's/\r$//' scripts/example.sh # strip the carriage returns in placechmod +x scripts/example.shעוד: git/hygiene (.gitattributes) ו-debugging/env-cards.
להריץ script שאינו executable: bash script.sh
Section titled “להריץ script שאינו executable: bash script.sh”קובץ חדש לא מקבל הרשאת הרצה. ./script.sh נכשל, ו-bash script.sh עובד תמיד.
./promote.sh dev x # bash: ./promote.sh: Permission denied (exit 126)bash promote.sh dev x # works; the file does not need +xchmod +x promote.sh # or make it executable oncecd בתוך script
Section titled “cd בתוך script”כל פקודה רצה בתיקייה הנוכחית של התהליך. אחרי git clone אתה עדיין מחוץ ל-clone.
git clone -q "$CLONE_URL" "$WORK/gitops"cd "$WORK/gitops" # without this, git add/commit run in the WRONG directoryprintf 'defaultImageTag: "%s"\n' "$CANDIDATE" > "apps/trident/versions/$ENV.yaml"function, for, case
Section titled “function, for, case”שלושה מבנים שמספיקים לכמעט כל script.
log() { printf '[INFO] %s\n' "$*"; } # function: "$*" = all arguments
for env in dev staging prod; do log "env $env"; done # loop over words
case "${1:-}" in # choose by pattern dev|staging) log "non-prod" ;; prod) log "production" ;; *) log "unknown: ${1:-}" >&2; exit 1 ;;esacמלכודת: ב-case כל ענף נגמר ב-;;, ו-*) תופס את כל השאר. ב-function, local x=... שומר משתנה בתוך הפונקציה בלבד.
here-doc — כתיבת קובץ מרובה שורות
Section titled “here-doc — כתיבת קובץ מרובה שורות”<<'EOF' מעביר בלוק טקסט ל-stdin של פקודה. עם מרכאות סביב EOF ה-shell לא מרחיב משתנים בפנים.
cat > values.yaml <<'EOF'image: tag: dev-1EOFcat > note.txt <<EOFwithout quotes the shell expands: $HOMEEOFמלכודת: שורת הסיום חייבת להיות EOF לבד, בלי רווחים לפניה או אחריה.