Подтвердить что ты не робот

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, но с эхом он просто печатает первую строку, а затем выходит.

4b9b3361

Ответ 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 и был прерван (как поведение по умолчанию) вместо работает 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, и это решило проблему. Надеюсь, он сможет больше объяснить это поведение.