Несмотря на повсеместное использование графики, shell не теряет своей актуальности и по сей день. А порой позволяет выполнять операции значительно быстрее и проще, нежели в графическом окружении. Однако есть множество вещей, о которых большинство даже не подозревает.
Я бы не хотел привязываться к какому-то определённому шеллу, тем не менее не каждая из рассмотренных ниже возможностей может быть POSIX совместима, однако гарантировано будет работать в ksh/bash/zsh.
1. Переменные и test
Ни для кого не секрет, что в shell можно сравнивать строки, числа и даже переменные. (:
[[ 2 -eq 3 ]]
[[ "test" == "test" ]]
[[ $VAR -eq 3 ]]
Но вот последний вариант, после случайной опечатки (забыл $ перед VAR) заставил поподробнее изучить поведение в данном случае, т.к. к моему удивлению конструкция отработала без ошибок и значение VAR подставилось как если бы я не забыл $. Более того, если в VAR сослаться на другую переменную, то всё прекрасно работает.
Разработчики даже от рекурсии не забыли защититься:
$ V=V ; [[ V -eq 12 ]]
-bash: [[: V: expression recursion level exceeded (error token is "V")
expansion, arithmetic expansion, command substitution, process substitution, and quote removal are performed.
…
Arithmetic Expansion
…
The evaluation is performed according to the rules listed below under ARITHMETIC EVALUATION.
…
ARITHMETIC EVALUATION
…
Within an expression, shell variables may also be referenced by name without using the parameter expansion syntax.
2. Достать переменные из subshell'a без сторонних утилит
Допустим у нас есть простая конструкция:
do_something | while read LINE ; do export VAR_N=${LINE##%*} ; done
Новичкам частенько приходится ломать голову, почему же VAR_N не доступна после завершения конструкции. Дело в том, что для цикла while создаётся subshell из которого переменные до родителя уже не доходят. Чтобы достать нужные нам переменные из цикла приходится изрядно попотеть. Все предлагаемые в интернетах варианты, как правило, сводятся либо к запоминанию вывода в переменную и многократное распарсивание:
VAR=$(cycle)
VAR_N=$(echo "$VAR"|sed 'magic_sed')
работает, но как-то некрасиво. Да и всякие sed'ы, perl'ы и прочие радости жизни приходится многократно вызывать, что явно не в лучшую сторону сказывается на производительности. Да и зачем они все, если можно обойтись без них?
Просто нужно корректно софрмировать вывод с красивым разделителем. Например так:
OLD__IFS="$IFS"
IFS='~' #или любой другой разделитель
set -- $(cycle)
VAR_N="$1"
VAR_NN="$2"
IFS="$OLD__IFS"
И гораздо нагляднее, и исправить если что в разы проще, чем каждый раз переделывать регулярки.
3. Спрятать часть данных от пайпа
Порой возникает ситуация, когда часть данных нужно спрятать от одного из пайпов, а потом соединить со всем потоком обратно. В такой ситуации скрытые данные можно перенаправить в stderr, а потом из stderr вернуть обратно в stdin:
$ ( { echo DATA ; echo HIDDEN_DATA >&2 ; } | sed 's/^/MODIFIED_/' ) 2>&1 | sed s/$/_CATCHED/
HIDDEN_DATA_CATCHED
MODIFIED_DATA_CATCHED
4. Я хочу парсить историю команд
Наряду с любителями парсить вывод ls, находятся ещё и любители парсить историю команд (для дальнейшей автоматической обработки результата), но они даже не догадываются о чём-нибудь кроме history, более того, совершенно не принимают во внимание, что вывод history на ура кастомизируется. Тут можете задать вопрос на засыпку: «а чем же ещё можно посмотреть history?» (ответ специально под спойлер спрячу) (:
5. Я помню, что if .. then .. else .. fi
отличается от .. && .. || ..
Некоторые любители порефакторить рвутся сделать код как можно более компактным. Особенно забавно наблюдать взаимозамены вышеназванных конструкций с последующими отладками.
Эти две конструкции не просто разные, они совершенно разные, хотя внешне может казаться, что делают одну вещь.
if $(condition); then com_1; else com_2; fi
condition && com_1 || com_2
Неочевидная разница в том, что в первом случае com_2 будет выполнена лишь в одном случае: condition вернёт false.
Во втором случае com_2 будет запущена если condition вернёт false, а так же в том случае, если com_1 вернёт ошибку. Не верите? убедитесь сами:
$ if true ; then false ; else echo 'hello' ; fi
$ true && false || echo 'hello'
hello
Ну вот пожалуй и всё. Хотел было замолвить пару слов про любимый sed, но уже и так как-то длинно получилось. Может в другой раз (:
Автор: sledopit