Ми вже бачили, як писати прості команди, де за командою йдуть аргументи. Тепер побачимо, як створювати складніші команди, поєднуючи прості.
Перш ніж почати, коротко про ввід і вивід.
Процеси мають «стандартні канали вводу-виводу».
Команда tr - дуже чистий приклад цього.
Її єдина робота - читати текст зі свого входу, виконувати заміну символів і друкувати отриманий текст на свій вихід.
Про те, як працювати з каналами «stdio», докладніше розповімо далі в силабусі.
Це одна з «козирних карт» програмування оболонки. Конвеєри дають змогу створювати складні перетворення потоку тексту.
Щоб отримати відсортований список користувачів:
cat /etc/passwd | cut -d : -f 1 | sort
Символ конвеєра (|) зʼєднує вихідні дані однієї команди з вхідними даними іншої.
cut читає вихідні дані cat, а sort читає вихідні дані cut.
Типово кожна команда в конвеєрі виконується в окремій підоболонці. (Підоболонка - це дочірній процес, що є копією поточної оболонки.)
Виконання конвеєрів коштує продуктивності.
Якщо в нас утворилися довгі конвеєри зі схожих команд, варто обʼєднати їх в одну команду.
Наприклад, конвеєри, у яких багато разів використовуються 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
Використовуймо ці логічні оператори ощадливо. Вони швидко роблять код нечитабельним або логіку важкою для розуміння.
Наприклад, може здатися, що це одне й те саме:
if A; then B; else C; fi
A && B || C
Різниця ось у чому: коли виконується C?
Ось кілька прикладів, де списки команд спрощують код.
Уявімо, що в нас є файл із даними про трикутники, де кожен трикутник записано трьома окремими рядками з довжинами його сторін.
$ 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")
Продовження рядка - це послідовність із двох символів: «бекслеш» і «символ нового рядка» (\ + \n).
Коли Bash бачить цю послідовність, він просто вилучає її з коду й тим самим продовжує поточний рядок наступним.
Стежмо, щоб між бекслешем і символом нового рядка не було пробілів.
Ось ще один приклад
printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort
або
printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort