Треки
/
Bash
Bash
/
Салабус
/
Конвеєри та списки команд
Ко

Конвеєри та списки команд у Bash

{one: "1 вправа", few: "%{count} вправи", many: "%{count} вправ", other: "%{count} вправи"}

Про концепцію Конвеєри та списки команд

Ми вже бачили, як писати прості команди, де за командою йдуть аргументи. Тепер побачимо, як створювати складніші команди, поєднуючи прості.

Ввід і вивід

Перш ніж почати, коротко про ввід і вивід.

Процеси мають «стандартні канали вводу-виводу».

  • Процес може споживати вхідні дані зі «stdin».
  • Процес може видавати вихідні дані у «stdout».
  • Процес може видавати вихідні дані про помилки у «stderr».

Команда tr - дуже чистий приклад цього. Її єдина робота - читати текст зі свого входу, виконувати заміну символів і друкувати отриманий текст на свій вихід.

Про те, як працювати з каналами «stdio», докладніше розповімо далі в силабусі.

Конвеєри

Це одна з «козирних карт» програмування оболонки. Конвеєри дають змогу створювати складні перетворення потоку тексту.

Щоб отримати відсортований список користувачів:

cat /etc/passwd | cut -d : -f 1 | sort

Символ конвеєра (|) зʼєднує вихідні дані однієї команди з вхідними даними іншої. cut читає вихідні дані cat, а sort читає вихідні дані cut.

Advanced
  • Типово кожна команда в конвеєрі виконується в окремій підоболонці. (Підоболонка - це дочірній процес, що є копією поточної оболонки.)

  • Виконання конвеєрів коштує продуктивності. Якщо в нас утворилися довгі конвеєри зі схожих команд, варто обʼєднати їх в одну команду. Наприклад, конвеєри, у яких багато разів використовуються grep, cut, sed, awk і tr, зазвичай можна обʼєднати в одну команду awk заради ефективності.

  • Статус завершення конвеєра - це статус завершення останньої команди в конвеєрі. Це означає, що якщо якась попередня команда зазнала невдачі, а остання завершилася успішно, статус завершення всього конвеєра дорівнює нулю.

    Є налаштування оболонки, яке може це змінити; якщо ввімкнено налаштування «pipefail» (за допомогою set -o pipefail), статус завершення конвеєра буде таким:

    • нуль, якщо всі команди завершилися успішно, інакше
    • останній ненульовий статус завершення команд у конвеєрі.

Списки команд

Список команд - це послідовність конвеєрів, розділених ; (або символом нового рядка), && (логічне і) або || (логічне або).

  • A; B - це список команд, де B виконується після завершення A.
  • A && B, де B виконується лише тоді, коли A завершується успішно (зі статусом нуль).
  • A || B, де B виконується лише тоді, коли A зазнає невдачі (завершується з ненульовим статусом).

Статус завершення списку команд - це статус завершення останньої виконаної команди.

Оператори && (і) та || (або) можна обʼєднувати в ланцюжок, щоб наступна команда виконувалася умовно, залежно від статусу попередніх команд. Наприклад

A && B && C && D || E
  • B виконується, якщо A завершується успішно,
  • C виконується, якщо A і B завершуються успішно,
  • D виконується, якщо A, B і C завершуються успішно,
  • E виконується, якщо хоч одна з команд A, B, C або D зазнає невдачі.
Caution

Використовуймо ці логічні оператори ощадливо. Вони швидко роблять код нечитабельним або логіку важкою для розуміння.

Наприклад, може здатися, що це одне й те саме:

if A; then B; else C; fi
A && B || C

Різниця ось у чому: коли виконується C?

  • У першому фрагменті (умовній конструкції) C виконається лише тоді, коли A зазнає невдачі.
  • У другому фрагменті C виконається, якщо A зазнає невдачі або якщо A завершиться успішно, але B зазнає невдачі!

Застосування списків команд

Ось кілька прикладів, де списки команд спрощують код.

Читання блоків рядків із файлу

Уявімо, що в нас є файл із даними про трикутники, де кожен трикутник записано трьома окремими рядками з довжинами його сторін.

$ cat triangle.dat
3
4
5
9
12
14

Можна використати цикл while, умовою якого будуть три окремі команди read:

while read a && read b && read c; do
    if is_pythagorean "$a" "$b" "$c"; then
        echo "$a:$b:$c is pythagorean"
    else
        echo "$a:$b:$c is not pythagorean"
    fi
done < triangle.dat

Якщо припустити, що is_pythagorean - це команда, яка визначає, чи задовольняють три сторони рівняння Піфагора, вихідні дані будуть такими:

3:4:5 is pythagorean
9:12:14 is not pythagorean
Твердження

Багато мов програмування мають таку форму твердження: якщо деяка умова не виконується, викидається виняток

assert(user == "Administrator", "wrong user");

У Bash можна скористатися логічним оператором «або», щоб відтворити цю функцію:

die () { # a little convenience function
    echo "$*" >&2
    exit 1
}

[[ $user == "Administrator" ]] || die "wrong user"
[[ $password == "secret!" ]]   || die "incorrect password"

Міркування про стиль

Довгі списки команд досить швидко стають важкими для читання. Щедре використання символів нового рядка може дуже допомогти.

Погляньмо на приклад, де слово додається до масиву, якщо виконуються дві умови.

[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")

Bash дозволяє додати символ нового рядка після символу конвеєра або логічного оператора.

[[ "$word" != "$topic" ]] &&
[[ "$key" == "$(sorted "$topic")" ]] &&
anagrams+=("$candidate")

Однак оператор у кінці рядка легко не помітити. Скориставшись продовженням рядка, можна поставити оператор на початок, і тоді одразу видно, що список продовжується:

[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
Note

Продовження рядка - це послідовність із двох символів: «бекслеш» і «символ нового рядка» (\ + \n). Коли Bash бачить цю послідовність, він просто вилучає її з коду й тим самим продовжує поточний рядок наступним. Стежмо, щоб між бекслешем і символом нового рядка не було пробілів.

Ось ще один приклад

printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort

або

printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці