
Пользуешься командным интерпретатором каждый день? Готов решить несколько логических задачек и узнать что-то новое? Добро пожаловать под кат.
Часть представленных здесь задач не принесёт реальной пользы, так как затрагивает какие-то сложные граничные случаи. Другая же часть будет полезна тем, кто постоянно использует шелл и читает чужие скрипты.
Примечание: на момент написания статьи автор использовал bash 4.4.12(1)-release в подсистеме Linux на Windows 10. Сложность задач различная.
Потоки ввода-вывода
Задача 1
$ cat 1
The cake is a lie!
Wanted!
Cake or alive
$ cat 1 | head | tail | sed -e 's/alive/dead/g' | tee | wc -l > 1
Сколько строк будет в файле 1 после выполнения команды?
Ответ
1
Объяснение
После интерпретации команды, но до запуска всех программ bash работает с указанными потоками ввода-вывода. Таким образом файл
1
очищается перед запуском первой программы и cat
открывает уже очищенный файл. Задача 2
$ cat file1
I love UNIX!
$ cat file2
I don't like UNIX
$ cat file1 <file2
Что будет выведено на экран?
Ответ
I love UNIX!
Объяснение
Некоторые программы забивают на stdin, когда указаны файлы.
Задача 3
$ cat file
Just for fun
$ cat file 1>&2 2>/dev/null
Что будет выведено на экран?
Ответ
Just for fun
Объяснение
Есть заблуждение, что последовательность
bash обнаруживает последовательность
После обнаружения последовательности
bash выводит так же и поток ошибок, так что на мы обнаруживаем на экране текст файла.
1>&2
перенаправляет первый поток во второй, однако, это не так. Рассмотрим команду из задания. В начале интерпретации введённой команды таблица потоков выглядит так:0 | 1 | 2 |
stdin | stdout | stderr |
bash обнаруживает последовательность
1>&2
и копирует содержимое ячейки 2 в ячейку 1:0 | 1 | 2 |
stdin | stderr | stderr |
После обнаружения последовательности
2>/dev/null
интерпретатор записывает значение в ячейку 2, оставляя другие ячейки нетронутыми:0 | 1 | 2 |
stdin | stderr | /dev/null |
bash выводит так же и поток ошибок, так что на мы обнаруживаем на экране текст файла.
Задача 4
Как вывод stdout отправить на stderr, а вывод stderr, наоборот, на stdout?
Ответ
4>&1 1>&2 2>&4
Объяснение
Принцип ровно как и в предыдущей задаче. Именно поэтому нам требуется дополнительный поток для временного хранения.
Исполняемые файлы
Задача 5
Дан файл test.sh
#!/bin/bash
ls $*
ls $@
ls "$*"
ls "$@"
Выполняются команды:
$ ls
1 2 3 test.sh
$ ./test.sh 1 2 3
Что выведет скрипт?
Ответ
1 2 3
1 2 3
ls: cannot access '1 2 3': No such file or directory
1 2 3
Объяснение
Без кавычек переменные $* и $@ ничем не отличаются и раскрываются во все заданные позиционные аргументы скрипта, разделённые пробелом. В кавычках способ раскрытия меняется: $* превращается в "$1 $2 $3", а $@ в свою очередь в "$1" "$2" "$3". Так как файла «1 2 3» в каталоге нет, ls выводит ошибку
Задача 6
Создадим в текущей директории файл
-c
c правами 755 и таким содержимым:#!/bin/bash
echo $1
Обнулим переменную $PATH и попытаемся выполнить:
$ PATH=
$ -c "echo SURPRISE"
Что будет выведено на экран? Что произойдет, если повторить ввод последней команды?
Ответ
Первый раз будет выведено
SURPRISE
, второй раз echo SURPRISE
Объяснение
При пустом PATH шелл начинает искать файлы в текущем каталоге. -с как раз находится. Так как исполняемый файл — текстовый, считывается первая строчка на предмет шебанга. Команда собирется по шаблону:
Таким образом, перед выполнением наша команда выглядит так:
И, как следствие, выполняется совершенно не то, что мы хотели.
Если выполнить второй раз, то шелл подберёт информацию о -c из кэша и выполнит его уже верно. Единственный способ защититься от столь неожиданного эффекта — добавить два минуса в шебанг.
<shebang> <filename> <args>
Таким образом, перед выполнением наша команда выглядит так:
/bin/bash -c "echo SURPRISE"
И, как следствие, выполняется совершенно не то, что мы хотели.
Если выполнить второй раз, то шелл подберёт информацию о -c из кэша и выполнит его уже верно. Единственный способ защититься от столь неожиданного эффекта — добавить два минуса в шебанг.
Переменные
Задача 7
$ ls
file
$ cat <$(ls)
$ cat <(ls)
Что будет выведено на экран в первом и во втором случае?
Ответ
В первом будет выведено содержимое файла file, во втором — имя файла.
Объяснение
В первом случае выполняется подстановка
Во втором случае
После подстановки команда приобретёт вид:
cat <file
Во втором случае
<(ls)
будет заменён на именованный пайп, соединённый входом с stdout ls, и выходом с stdin cat.После подстановки команда приобретёт вид:
cat /dev/fd/xx
Задача 8
$ TEST=123456
$ echo ${TEST%56}
Что будет выведено на экран?
Ответ
1234
Объяснение
При такой записи матчится паттерн (# — с начала переменной; ## — жадно с начала переменной; % — с конца переменной; %% — жадно с конца переменной) и удаляется при подстановке. Содержимое переменной при этом остаётся нетронутым. Таким образом, например, удобно получать имя файла без расширения.
$ TEST=file.ext
$ echo ${TEST%.ext}
file
Задача 9
$ echo ${friendship:-magic}
Что будет выведено на экран?
Ответ
Если переменная friendship определена, то содержимое переменной. Иначе — magic.
Объяснение
В документации эта магия называется «unset or null» и позволяет использовать заданное дефолтное значение переменной в одну строчку.
Порядок выполнения
Задача 10
while true; false; do
echo Success
done
Что будет выведено на экран?
Ответ
Ничего
Объяснение
Операторы while и if позволяют в условие запихать целую последовательность действий, однако результат (код возврата) будет учитываться только у последней команды. Так как там стоит false, цикл даже не начнётся.
Задача 11
$ false && true || true && false && echo 1 || echo 2
Что будет выведено на экран?
Ответ
2
Объяснение
Добавим скобочек для явного порядка и упростим команду, принимая во внимание, что учитывается только код возврата последней команды:
((((false && true) || true) && false) && echo 1) || echo 2
(((false || true) && false) && echo 1) || echo 2
((true && false) && echo 1) || echo 2
(false && echo 1) || echo 2
false || echo 2
echo 2
Замечания, пожелания и дополнительные задачи приветствуются в комментарии или ЛС.