Bash: поведение Head & Tail с помощью bash script
Предположим, что у меня есть script: -
test.sh
#!/bin/bash
command1 #prints 5 lines
command2 #prints 3 lines
Я запустил script с помощью test.sh|head -n5
Что произойдет в этом случае? Будут ли выполняться обе команды? или он остановится после команды1?
Что, если я назову его -n1?
Фон: Я могу задавать очень простой вопрос, но я действительно заметил что-то интересное. Мой script (другой) обрабатывал 7000 файлов, и каждый файл выдавал 1 строку вывода. Для полного выполнения script требуется 7 минут, но при выполнении head -n1 мне сразу же предложили сразу же, как script завершил обработку только первого файла
Edit: Ниже приведен мой script
for i in $(ls filepath);do
echo "$i" # issue here
python mySript "$i" > "/home/user/output/""$i"".out"
fi
done
Удаление echo выше позволяет script работать полными 7 минутами с помощью head -n1, но с эхом он просто печатает первую строку, а затем выходит.
Ответы
Ответ 1
Это довольно интересная проблема! Спасибо, что опубликовали его!
Я предположил, что это происходит, когда head
выходит после обработки первых нескольких строк, поэтому сигнал SIGPIPE
отправляется на bash запустит script, когда он попытается echo $x
в следующий раз. Я использовал RedX script, чтобы доказать эту теорию:
#!/usr/bin/bash
rm x.log
for((x=0;x<5;++x)); do
echo $x
echo $x>>x.log
done
Это работает, как вы описали! Используя t.sh|head -n 2
, он записывает только 2 строки на экран и в x.log. Но захват SIGPIPE изменяет это поведение...
#!/usr/bin/bash
trap "echo SIGPIPE>&2" PIPE
rm x.log
for((x=0;x<5;++x)); do
echo $x
echo $x>>x.log
done
Вывод:
$ ./t.sh |head -n 2
0
1
./t.sh: line 5: echo: write error: Broken pipe
SIGPIPE
./t.sh: line 5: echo: write error: Broken pipe
SIGPIPE
./t.sh: line 5: echo: write error: Broken pipe
SIGPIPE
Ошибка записи возникает, когда stdout
уже закрыт, так как другой конец канала закрыт. И любая попытка записи на закрытый канал вызывает сигнал SIGPIPE, который по умолчанию завершает программу (см. man 7 signal
). Теперь x.log содержит 5 строк.
Это также объясняет, почему /bin/echo
решил проблему. См. Следующий script:
rm x.log
for((x=0;x<5;++x)); do
/bin/echo $x
echo "Ret: $?">&2
echo $x>>x.log
done
Выход:
$ ./t.sh |head -n 2
0
Ret: 0
1
Ret: 0
Ret: 141
Ret: 141
Ret: 141
Десятичная позиция 141 = hex 8D. Hex 80 означает, что сигнал получен, hex 0D для SIGPIPE. Поэтому, когда /bin/echo
пытался записать в stdout, он получил SIGPIPE и был прерван (как поведение по умолчанию) вместо bash работает script.
Ответ 2
Приятное открытие. По моим испытаниям это точно так же, как вы сказали. Например, у меня есть этот script, который просто ест cpu, чтобы определить его в top
:
for i in `seq 10`
do echo $i
x=`seq 10000000`
done
Проводя script с помощью head -n1
, мы видим команду, возвращающуюся после первой строки. Это поведение head
: он завершил свою работу, поэтому он может остановить и вернуть управление вам.
Ввод script должен продолжаться, но посмотрите, что происходит: когда возвращается head
, его pid больше не существует. Поэтому, когда Linux пытается отправить вывод script в процесс заголовка, он не находит этот процесс, поэтому script падает и останавливается.
Попробуйте использовать его с помощью python script:
for i in xrange(10):
print i
range(10000000)
При запуске и проводе на голову у вас есть следующее:
$ python -u test.py | head -n1
0
Traceback (most recent call last):
File "test.py", line 2, in <module>
print i
IOError: [Errno 32] Broken pipe
Параметр -u
указывает python автоматически стирать stdin и stdout, как это сделал бы bash. Таким образом, вы видите, что программа на самом деле останавливается с ошибкой.
Ответ 3
Это скорее комментарий, чем ответ, но слишком большой для комментария.
Я пробовал следовать script:
#!/usr/bin/env bash
rm -f "test_head.log"
echo "1 line"
echo "1 line" >> "test_head.log"
echo "2 line"
echo "2 line" >> "test_head.log"
echo "3 line"
echo "3 line" >> "test_head.log"
echo "4 line"
echo "4 line" >> "test_head.log"
echo "5 line"
echo "5 line" >> "test_head.log"
echo "6 line"
echo "6 line" >> "test_head.log"
echo "7 line"
echo "7 line" >> "test_head.log"
echo "8 line"
echo "8 line" >> "test_head.log"
Затем я запустил script с помощью:
./test_head.sh | head -n1
Выход кошки (к моему удивлению):
1 строка
Я понятия не имею, что происходит.
После прочтения комментария @ymonad я попробовал его и заменил echo
на /bin/echo
, и это решило проблему. Надеюсь, он сможет больше объяснить это поведение.