Как стать автором
Обновить

Комментарии 31

Не понял, откуда в восьмой задаче в выводе взялись цифры 8 и 9.

Когда изменял задания, забыл перенести ответ. Исправил, благодарю
Firemoon
а разве при перенаправлении потока баш хоть что-то возвращает?
мне казалось, что вообще никогда ничего не выплевывает в консоль
добавим в ваш код не перезапись файла, а добавление.
cat 1 | head | tail | sed -e 's/alive/dead/g' | tee | wc -l >> 1
:~# cat 1
The cake is a lie!
1
а разве при перенаправлении потока баш хоть что-то возвращает?

Эээ. Смотря какого потока и куда.


добавим в ваш код не перезапись файла, а добавление.

И всё заработает, потому что баш откроет файл на дозапись, и содержимое не пострадает.

ну сейчас у вас почему-то другой вопрос
Сколько строк будет в файле 1 после выполнения команды?
но и тут же опять не правильно.
в вашем пример строк будет 1
и в ней будет записан будет 0(число)
а разве при перенаправлении потока баш хоть что-то возвращает?
мне казалось, что вообще никогда ничего не выплевывает в консоль

Все верно, первая задача некорректно составлена.


$ cat 1
The cake is a lie!
$ cat 1 | head | tail | sed -e 's/alive/dead/g' | tee | wc -l > 1 

Ничего на экран не выведет, так же как и (здесь важно > 2 в конце):


$ cat 1
The cake is a lie!
$ cat 1 | head | tail | sed -e 's/alive/dead/g' | tee | wc -l > 2

Не зависимо от открытия/очищения файлов.
Просто потому что &1 направлен в файл вместо stdout.

По поводу 3 задачи.

Есть заблуждение, что последовательность 1>&2 перенаправляет первый поток во второй, однако, это не так.

bash обнаруживает последовательность 1>&2 и копирует содержимое ячейки 2 в ячейку 1

Ой ли?
Здесь вот совсем другое говорят:
i>&j
# Redirects file descriptor i to j.
# All output of file pointed to by i gets sent to file pointed to by j.


В вашем примере stdout перенаправляется и в 1 и в 2 дескрипторы. Затем 2 перенаправляется в /dev/null. Но 1 как содержал в себе stdout, так и содержит.
Именно. Правда корректнее считать, что 1 содержал в себе не STDOUT, а уже конкретный терминал типа /dev/pts/1
Хм. Давайте разберёмся.
Сначала цитата про заблуждение. Я встречал людей, которые ещё недостаточно постигли работу с шеллом и поэтому думают, что перенаправление происходит именно потоков, то есть `1>&2` в их понимании значит «слить во второй поток, второй поток сам разберётся», именно на них нацелена данная задача.

Далее, то, что говорят [вон там](https://www.tldp.org/LDP/abs/html/io-redirection.html).
> gets sent to file pointed to by j.

То есть перенаправление происходит в файл, на который указывает в данный момент j-тый дескриптор. Если j-тый дескриптор станет указывать на другой файл, i-тый останется без изменений.

>В вашем примере stdout перенаправляется и в 1 и в 2 дескрипторы. Затем 2 перенаправляется в /dev/null. Но 1 как содержал в себе stdout, так и содержит.

А вот тут, честно, не понял, откуда stdout? В объяснении есть табличка с дескрипторами.

UPD: erwin_shrodinger, пардон, я промахнулся веткой.
А вот тут, честно, не понял, откуда stdout? В объяснении есть табличка с дескрипторами.

Согласен, написал некорректно. Имелось в виду вот что.
По вашему утверждению (судя по табличке) ситуация будет такая:
0 ~ stdin
1 ~ stderr
2 ~ stderr
Я же утверждаю обратное:
0 ~ stdin
1 ~ stdout
2 ~ stdout
В таком случае, после 2>/dev/null мы не теряем stdout, так как можем получить его из 1-го дескриптора.

Т.е. в целом мне ваш «финт» с 1>&2 кажется нефункциональным и уж тем более не копирующим содержимое ячейки 2 в ячейку 1.

UPD: Вот тут вы сами же это подтверждаете:
То есть перенаправление происходит в файл, на который указывает в данный момент j-тый дескриптор. Если j-тый дескриптор станет указывать на другой файл, i-тый останется без изменений
STDOUT и STDERR это названия потоков для программиста, который пишет программу.
В то время как у запущенного процесса уже нет STDIN, STDOUT, STDERR — есть открытые файловые дескрипторы 0, 1, 2 которые на при создании терминала сразу ассоциируются с конкретным устройством.

Поэтому переключая 2 в 1 вы переключаете не STDERR в STDOUT, а 2 дескриптор в то, куда сейчас смотрит 1-й дескриптор.
Посмотреть куда смотрят дескрипторы можно в директории fd процесса (file descriptors) — вот так
ls -l /proc/$$/fd
Да, так корректнее звучит) спасибо
Первая же задача

$ cat 1 | head | tail | sed -e 's/alive/dead/g' | tee | wc -l > 1


После интерпретации команды, но до запуска всех программ bash работает с указанными потоками ввода-вывода. Таким образом файл 1 очищается перед запуском первой программы и cat открывает уже очищенный файл.


Не совсем согласен с объяснением. Да, файл будет очищен, но данная команда в конечном счете весь вывод на экран перенаправляет в > 1, следовательно на экран не будет ничего выведено, даже если в конце перенаправить в другой файл (например > 2.txt), не затирая 1.

Согласен. Доберусь до компа — добавлю текста в исходный файл и перепишу задание на "сколько строчек будет в файле 1?" Так будет лучше

сколько строчек будет в файле 1


wc -l
— всегда 1 строка.
Задача 3
$ cat file 1>&2 2>/dev/null
just for fun

Объяснение
Есть заблуждение, что последовательность 1>&2 перенаправляет первый поток во второй, однако, это не так.


Не совсем согласен с объяснением. IMHO правильнее говорить, что вывод из программы у нас идет не в STDOUT и STDERR, а вот с точки зрения запущенной в консоли программы, она пользуется дескрипторами 1 и 2, которые можно посмотреть вот так
ls -l /proc/$$/fd
То можно увидеть, что эти дескрипторы — просто ссылки на /dev/pts/X.
Поскольку мы сперва перенаправляем 1>&2, когда у нас &2 все еще /dev/pts/X, он будет перенаправлен в /dev/pts/X (ничего не изменится), и только затем мы меняем 2>/dev/null.
Если же поменять перенаправления местами, то 1> тоже перенаправится в /devnull, поскольку к этому моменту дескриптор 2 будет уже ссылаться на него.
$ cat file 2>/dev/null 1>&2
Такая команда ничего не выведет.
Задача 4
Как вывод stdout отправить на stderr, а вывод stderr, наоборот, на stdout?

Ответ
4>&1 1>&2 2>&4

Объяснение
Принцип ровно как и в предыдущей задаче. Именно поэтому нам требуется дополнительный поток для временного хранения.


Опять не очень понятная задача — у нас нет STDERR и STDOUT с точки зрения консоли, есть открытые файловые дескрипторы 1 и 2, которые уже куда-то ссылкаются. Чтобы поменять их местами, можно просто посмотреть куда конкретно они ссылкаются и назначить. То есть да, указанная команда сработает, но объяснение — не совсем понятное почему оно так сработает.
P.S. А вообще — статья полезная!
Сейчас в комментариях разберемся как следует и накажем кого попало!
НЛО прилетело и опубликовало эту надпись здесь
Что использовать вместо bash?
НЛО прилетело и опубликовало эту надпись здесь
в POSIX sh файловые дескрипторы работают точно также.
11 способов выстрелить себе в ногу bash.

Пожалуйста, объясните, что именно кэшируется в задаче 6, и как это на результат работы влияет?

Bash кеширует полный путь ко всем выполняемым командам. Вы можете посмотреть текущий кеш командой hash (команда встроенная, справка по help hash)

Ключевой момент — это то, что при пустом PATH, баш начинает искать исполняемые файлы в текущем каталоге, поэтому он найдет файл -c и выполнит его, затем выполнить вторую команду (в bash можно выполнить две команды, разделенные пробелом)

То есть первый вызов команды
-с «echo Hello»
будет на самом деле вызов двух независимых команд:
"./-c" и «echo Hello»
Первая не выведет ничего, так как ей не будет передан аргумент, а вторая выведет Hello
Но в кеш у нас сохранится -c, которая находится в ./-c. И в следующий раз оно будет выполнять -c уже как вызов скрипта, а не как команду. А при вызове скрипта, ему будут передаваться аргументы

Во второй раз команда
-c «echo Hello»
будет выполняться уже как:
./-c «echo Hello»

Следовательно будет выполнен echo «echo Hello» из скрипта

В этом примере используется как минимум три малоизвестных нюанса
1. Например выполнение команд через пробел. Посмотрите пример ниже:
скрипт show_var.sh (не забудьте chmod 755 show_var.sh)
#!/bin/bash
echo $MYVAR

в консоли
$ MYVAR=123 ./show_var
123
$ echo $MYVAR


2. То, что при пустом PATH поиск исполняемых файлов происходит в текущем каталоге — я сам не знал.

3. То, что выполненные внешние команды кешируются в bash (путь к ним)
Не соглашусь с некоторыми тезисами в объяснении. Ключевое в задаче №6 то, как происходит запуск скрипта и передача аргументов ему же. Первым аргументом команде, прописанной в shebang (#!/bin/bash) передаётся собственно имя скрипта. При первом запуске передаётся "-c", что интерпретируется как аргумент командной строки, выполняющий следующую за ним команду внутри bash. Т.е. полная команда будет выглядеть так: '/bin/bash -c «echo SURPRISE»'. При последующих запусках bash берёт уже путь из кеша, и команда будет выглядеть так: '/bin/bash ./-c «echo SURPRISE»'. Здесь "./-c" уже интерпретируется как имя скрипта для выполнения, а всё что дальше — как аргументы скрипта.

Выполнение команд через пробел работает, насколько мне известно, только для установки переменных окружения.

Вообще, передача "-c" как имени файла при пустом кеше выглядит как баг.
При первом запуске никому ничего не передается.
Выполняется команда "-c", а не шебанг и имя скрипта.

Еще раз.
1. Залогинились,
2. У нас в каталоге есть исполняемый файл "-c"
3. Обнулили PATH
Выполняем
-c «echo Hello»

Как эту строку разбирает интерпретатор?
Он просто начинает выполнять команды по очереди. Команды через пробел — тоже команды, и он выполняет команду "-c"
На этом этапе он понятия не имеет, что это за команда — внутренней такой команды нет, внешней в кеше нет, поэтому он просто пытается ее выполнить. Так как при пустом PATH у нас происходит поиск команд в текущем каталоге, запускается файл ./-c, который не выводит ничего — ему ничего не передавали.
Затем выполняется команда «echo Hello»

Теперь запускаем это во второй раз.
Интерпретатор видит "-c", но у него в кеше есть путь ./-c — поэтому он знает, что это внешний исполняемый файл. А если это внешний исполняемый файл, то значит ему всю оставшуюся строку нужно передать как аргументы. В прошлый раз он этого не знал, поэтому и не передавал.

Собственно, если вы сомневаетесь — никто ж не мешает вам проверить на практике, И написать скрипт, который выводит не echo $1, а echo «trulyalya», и увидеть, что
-c «echo Hello»
при первом запуске выведет Hello, а не trulyalya
Приведу 3 примера, ставящих под сомнение утверждение о выполнении команд через пробел интерпретатором.

1. Попробуйте переименовать "-c" в, например, "-r". Первый и последующие запуски будут давать одинаковый результат.

2. Попробуйте следующий скрипт:
#!/bin/bash --

echo $1
Первый и последующие запуски также будут давать одинаковый результат.

3. Попробуйте следующий скрипт:
#!/bin/bash

/bin/uname
При первом запуске, несмотря на присутствие uname в скрипте, будет напечатано лишь «SURPRISE», при последующих — «Linux».
Firemoon Автор, как насчет результатов работы этих команд в других нетрадиционных шеллах типа ksh?)
Слишком нетрадиционно ;)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории