Системное администрирование
Занимательные задачки
Оболочки
13 июня 2018

Насколько хорошо ты знаешь bash?


Пользуешься командным интерпретатором каждый день? Готов решить несколько логических задачек и узнать что-то новое? Добро пожаловать под кат.

Часть представленных здесь задач не принесёт реальной пользы, так как затрагивает какие-то сложные граничные случаи. Другая же часть будет полезна тем, кто постоянно использует шелл и читает чужие скрипты.

Примечание: на момент написания статьи автор использовал 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

Объяснение
Есть заблуждение, что последовательность 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 шелл начинает искать файлы в текущем каталоге. -с как раз находится. Так как исполняемый файл — текстовый, считывается первая строчка на предмет шебанга. Команда собирется по шаблону:

<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


Замечания, пожелания и дополнительные задачи приветствуются в комментарии или ЛС.

+35
29,9k 209
Комментарии 31

Рекомендуем