PŘÍKAZOVÁ ŘÁDKA · SKRIPTY · AUTOMATIZACE

Bash.
Od příkazu ke skriptu.

Praktická syntaxe, bezpečné postupy a příklady připravené ke kopírování. Bez zbytečné omáčky.

RYCHLÝ STARTbash skript.sh arg1 arg2
27 tématBash 4+ / GNU/Linux

Začínáme

Spuštění a shebang

#

Skript je textový soubor s příkazy. Shebang určí interpret a chmod přidá právo ke spuštění.

#!/usr/bin/env bash
set -Eeuo pipefail

echo "Ahoj, ${USER}!"

# spuštění
chmod +x skript.sh
./skript.sh
  • bash skript.sh nevyžaduje spustitelný příznak.
  • source skript.sh spustí obsah v aktuálním shellu.

Nápověda a typ příkazu

#

Zjistěte, zda je název alias, funkce, vestavěný nebo externí příkaz, a otevřete správnou dokumentaci.

type -a printf
command -V cd
help [[
help printf
man bash
printf --help

Proměnné

Proměnné a uvozovky

#

Přiřazení nemá mezery kolem =. Dvojité uvozovky expandují proměnné; jednoduché zachovají doslovný text.

name="Ada Lovelace"
count=3
printf '%s má %d zprávy\n' "$name" "$count"

echo '$HOME'   # doslova
echo "$HOME"   # hodnota
  • Expanze proměnných téměř vždy uzavírejte do dvojitých uvozovek.
  • readonly nastaví hodnotu jen pro čtení; unset proměnnou odstraní.

Poziční parametry

#

Argumenty skriptu jsou v $1, $2…; $# je jejich počet a "$@" bezpečně zachová každý argument zvlášť.

#!/usr/bin/env bash
printf 'skript: %s\n' "$0"
printf 'počet: %d\n' "$#"

for arg in "$@"; do
  printf '<%s>\n' "$arg"
done

shift  # odstraní $1

Expanze parametrů

#

Výchozí hodnoty, povinné proměnné, náhrady a práce s částmi řetězce bez externích programů.

${name:-host}       # výchozí hodnota
${name:=host}       # výchozí + přiřazení
${name:?chybí name} # chyba, pokud není
${#name}            # délka
${file%.txt}        # odebere nejkratší příponu
${path##*/}         # basename
${text/foo/bar}     # první náhrada
${text//foo/bar}    # všechny náhrady

Indexovaná pole

#

Bash pole uchovává více hodnot. Používejte "${array[@]}", aby se prvky nerozbily podle mezer.

colors=(red "dark blue" green)
colors+=(orange)

printf '%s\n' "${colors[1]}"
printf 'prvků: %d\n' "${#colors[@]}"

for color in "${colors[@]}"; do
  echo "$color"
done

Asociativní pole

#

Deklarace -A vytvoří mapu klíč → hodnota. Dostupné v Bashi 4 a novějším.

declare -A ports=(
  [http]=80
  [https]=443
  [ssh]=22
)
ports[postgres]=5432

printf '%s\n' "${ports[https]}"
for key in "${!ports[@]}"; do
  printf '%-10s %s\n' "$key" "${ports[$key]}"
done

Aritmetika

#

Uvnitř (( … )) se proměnné píší bez $. Výraz (( )) vrací úspěch, je-li výsledek nenulový.

((count++))
((total = price * quantity))

if (( total > 1000 )); then
  echo "velká objednávka"
fi

result=$(( (a + b) * 2 ))

Podmínky

Testy [[ … ]]

#

Rozšířený test Bashe zvládá řetězce, čísla, soubory i regulární výrazy bezpečněji než historický [ … ].

[[ -f $file ]]          # běžný soubor
[[ -d $dir ]]           # adresář
[[ -r $file ]]          # lze číst
[[ -n $text ]]          # neprázdný řetězec
[[ $a == $b ]]          # shoda
[[ $name == *.txt ]]    # glob
[[ $id =~ ^[0-9]+$ ]]   # regex
[[ $n -ge 10 ]]         # číslo ≥ 10
  • Uvnitř [[ ]] neuzavírejte pravou stranu == do uvozovek, pokud má fungovat jako glob.
  • Regex po =~ se obvykle píše bez uvozovek.

if a case

#

if rozhoduje podle návratového kódu příkazu. case je čitelný pro více řetězcových variant.

if grep -q 'ready' status.txt; then
  echo "připraveno"
elif [[ -s status.txt ]]; then
  echo "jiný stav"
else
  echo "bez stavu"
fi

case ${1:-} in
  start|run) start_service ;;
  stop)      stop_service ;;
  *)         echo "Použití: $0 {start|stop}" >&2; exit 2 ;;
esac

Cykly

Smyčka for

#

for prochází seznam hodnot. C-styl je vhodný pro číselné iterace.

for file in *.log; do
  [[ -e $file ]] || continue
  gzip -- "$file"
done

for ((i = 0; i < 5; i++)); do
  printf '%d ' "$i"
done

while read

#

Standardní způsob bezpečného čtení souboru po řádcích zachová mezery i poslední řádek bez newline.

while IFS= read -r line || [[ -n $line ]]; do
  printf '%s\n' "$line"
done < input.txt

# CSV bez citovaných čárek
while IFS=, read -r id name email; do
  printf '%s: %s <%s>\n' "$id" "$name" "$email"
done < users.csv

break, continue a select

#

break ukončí cyklus, continue přeskočí na další průchod. select vytvoří jednoduché očíslované menu.

select action in Start Stop Konec; do
  case $action in
    Start) start_service; break ;;
    Stop)  stop_service; break ;;
    Konec) exit 0 ;;
    *) echo "Neplatná volba" >&2 ;;
  esac
done

I/O a roury

Přesměrování

#

Deskriptor 0 je vstup, 1 standardní výstup a 2 chybový výstup. Pořadí přesměrování je důležité.

command > output.txt       # přepíše stdout
command >> output.txt      # připojí stdout
command 2> errors.txt      # stderr
command > all.txt 2>&1     # oba proudy
command &> all.txt         # totéž v Bashi
command < input.txt
command 2>/dev/null
  • >file 2>&1 pošle oba proudy do souboru; 2>&1 >file pošle stderr do původního stdout.

Roury a pipefail

#

Roura předává stdout vlevo na stdin vpravo. pipefail zajistí, že chyba uprostřed nezmizí.

set -o pipefail
journalctl -u nginx | grep -i error | tail -n 20

# PIPESTATUS zachová stav všech částí
printf '%s\n' data | grep foo | sort
printf '%s\n' "${PIPESTATUS[@]}"

Here-document a here-string

#

Víceřádkový vstup lze vložit přímo do skriptu. Citovaný oddělovač vypne expanze.

cat <<EOF
Uživatel: $USER
Domov: $HOME
EOF

cat <<'LITERAL'
$USER zůstane doslova
LITERAL

grep -o '[0-9]+' <<< "objednávka 142"

Substituce příkazu a procesu

#

$(…) vloží stdout příkazu do hodnoty. <(…) zpřístupní výstup jako dočasný souborový vstup.

today=$(date +%F)
files=$(find . -type f | wc -l)

diff <(sort old.txt) <(sort new.txt)

printf 'datum=%s, souborů=%s\n' "$today" "$files"

Funkce

Funkce a lokální proměnné

#

Funkce přijímá stejné poziční parametry jako skript. local zabrání nechtěné změně globální proměnné.

log() {
  local level=$1
  shift
  printf '[%(%F %T)T] %-5s %s\n' -1 "$level" "$*" >&2
}

add() {
  local a=$1 b=$2
  printf '%d\n' "$((a + b))"
}

result=$(add 20 22)
log INFO "výsledek: $result"

Volby s getopts

#

Vestavěný getopts zpracuje krátké volby bez externího programu.

verbose=false
output=output.txt

while getopts ':vo:' opt; do
  case $opt in
    v) verbose=true ;;
    o) output=$OPTARG ;;
    :) echo "Volba -$OPTARG vyžaduje hodnotu" >&2; exit 2 ;;
    ?) echo "Neznámá volba: -$OPTARG" >&2; exit 2 ;;
  esac
done
shift $((OPTIND - 1))

Robustní skripty

trap a úklid

#

trap spustí kód při ukončení nebo signálu. Dočasné prostředky tak nezůstanou ležet.

tmpdir=$(mktemp -d)
cleanup() {
  rm -rf -- "$tmpdir"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

# práce v $tmpdir

Přísnější režim

#

Kombinace -Eeuo pipefail odhalí řadu chyb, ale vyžaduje vědomé zacházení s očekávanými neúspěchy.

set -Eeuo pipefail
IFS=$'\n\t'

on_error() {
  local status=$? line=$1
  printf 'Chyba na řádku %d (kód %d)\n' "$line" "$status" >&2
}
trap 'on_error $LINENO' ERR
  • set -e nenahrazuje kontrolu chyb a má výjimky v podmínkách, rourách a seznamech.
  • Volitelné proměnné s nounset čtěte například jako ${name:-}.

Bezpečná práce se soubory

#

Názvy mohou obsahovat mezery, nové řádky nebo začínat pomlčkou. Používejte nulové oddělovače a --.

find . -type f -name '*.tmp' -print0 |
  while IFS= read -r -d '' file; do
    rm -- "$file"
  done

cp -- "$source" "$destination"
printf '%q\n' "$suspicious_name"

Ladění

#

-n kontroluje syntaxi bez spuštění, -x vypisuje expandované příkazy. PS4 může doplnit soubor a řádek.

bash -n skript.sh
ShellCheck skript.sh

PS4='+ ${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]:-main}: '
BASH_XTRACEFD=3
exec 3>trace.log
set -x
# laděný kód
set +x

Recepty

Rotovaná záloha

#

Praktický skript vytvoří komprimovaný archiv a ponechá jen sedm nejnovějších záloh.

#!/usr/bin/env bash
set -Eeuo pipefail

source_dir=${1:?Použití: $0 ADRESÁŘ CÍL}
backup_dir=${2:?Použití: $0 ADRESÁŘ CÍL}
mkdir -p -- "$backup_dir"
stamp=$(date +%Y%m%d-%H%M%S)
archive=$backup_dir/backup-$stamp.tar.gz

tar -czf "$archive" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"
mapfile -t old < <(find "$backup_dir" -maxdepth 1 -name 'backup-*.tar.gz' -printf '%T@ %p\n' | sort -nr | tail -n +8 | cut -d' ' -f2-)
((${#old[@]} == 0)) || rm -- "${old[@]}"

Nejčastější IP v logu

#

Roura vybere první pole z access logu, spočítá výskyty a zobrazí deset nejčastějších adres.

awk '{print $1}' /var/log/nginx/access.log |
  sort |
  uniq -c |
  sort -nr |
  head -n 10

Paralelní zpracování

#

xargs umí spustit několik úloh současně a s -0 bezpečně přijímá libovolné názvy souborů.

find images -type f -name '*.png' -print0 |
  xargs -0 -r -n 1 -P 4 bash -c '
    file=$1
    convert "$file" "${file%.png}.webp"
  ' _