esyr: (Default)
[personal profile] esyr
Вероятно, многие знают, что при помощи shell built-in read можно построчно читать всякое; особенно это актуально при считывании списка файлов, которые потенциально могут содержать пробелы (например, в выдаче ls или find) или чего-то подобного. Обычно это делается так:

Построчное чтение из файла:
while read line
do
  # code
done < file

Построчное чтение результата выполнения команды (вариант с перенаправлением):
ls | while read line
do
  # code
done

Вроде бы всё хорошо. Проблемы могут начаться в случае, когда во время чтения нужно изменять значения переменных. А именно, последний (и, кстати, наиболее часто используемый в случае, когда нужно обработать вывод команды, а не читать из файла) вариант приводит к созданию дочернего процесса (pipe же!), что приводит к тому, что все изменения переменных внутри тела цикла выполняются в подпроцессе и, как следствие, на процессе, в котором выполняется скрипт, не попадают. Это можно легко увидеть на следующем примере:
i=0; seq 1 3| while read line; do i=$(( $i + 1 )); done; echo $i
В bash и dash в результате будет выдан 0, в zsh и ksh (которые исполняют последний элемент пайплайна в текущем процессе в случае, если это shell built-in) — 3.
Обойти эту проблему можно несколькими способами. Один из них — использовать heredoc:
while read a b
do
  i=$(( $i + 1 ))
done <<EOF
`seq 1 3 | sed 's/^/1 /'`
EOF
echo $i
При этом, как можно видеть из примера выше, можно использовать произвольный пайплайн.
Очевидный недостаток этого решения — прежде, чем результат выполнения будет передан в цикл, он полностью будет получен. Это можно попытаться обойти, например, путём создания временного FIFO-файла:
fifofile=`mktemp`
rm "$fifofile"
mkfifo "$fifofile"

ls | sed 's/^/1 /' > "$fifofile" & # random command which output we need to parse
while read a b
do
  i=$(( $i + 1 ))
done < "$fifofile"
echo $i

rm "$fifofile"
Очевидно, что в данном случае строчка rm "$fifofile" будет выполнена после завершения цикла, что, по идее (так как read ждёт закрытия потока ввода), произойдёт только после завершения процесса, пишущего в FIFO и считывания из него всех данных.
Но всё же ощущается неаккуратность в виде наличия временных файлов. Тем более, что упражнение вида mktemp—rm—mkfifo теоретически может породить рейс. Можно попробовать соорудить конструкцию с созданием дескрипторов (like, exec 3<&0>&1):
exec 3<&0>&1

ls | sed 's/^/1 /' >&3 & # random command which output we need to parse
while read -u 3 a b
do
  i=$(( $i + 1 ))
done
echo $i

exec 3<&->&-

И, казалось бы, счастье есть: мы можем запихать произвольный пайплайн из процессов в этот дескриптор, а потом читать из него (или с помощью специального ключика у read, как в примере выше, или же просто <&3. Осталась одна маленькая проблема: если попытаться прибить скрипт до окончания его работы, то это ему ничуть не помешает. Посему, нужно повесить trap, который убивает дочерний процесс и завершает работу:
trap 'kill -9 $cpid; exit' TERM INT

exec 3<&0>&1

ls | sed 's/^/1 /' >&3 & # random command which output we need to parse
cpid=$!
while read -u 3 a b
do
  i=$(( $i + 1 ))
done
echo $i

exec 3<&->&-

Более аккуратного варианта как-то в голову не пришло. Если кто знает — поделитесь.

UPD. Таки приведённый вариант с дескрипторами работает только в zsh (не в dash). Буду думать, как его таки заимплементить.
From:
Anonymous( )Anonymous This account has disabled anonymous posting.
OpenID( )OpenID You can comment on this post while signed in with an account from many other sites, once you have confirmed your email address. Sign in using OpenID.
User
Account name:
Password:
If you don't have an account you can create one now.
Subject:
HTML doesn't work in the subject.

Message:

 
Notice: This account is set to log the IP addresses of everyone who comments.
Links will be displayed as unclickable URLs to help prevent spam.

Profile

esyr: (Default)
esyr

October 2010

S M T W T F S
     12
3456789
10111213141516
17181920212223
24252627282930
31      

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jul. 22nd, 2017 06:34 am
Powered by Dreamwidth Studios