null

Программирование на shell: использование read через конвеер

Не смотря на разнообразие различных языков некоторые задачи традиционно проще и быстрее решить используя возможности стандартного shell. Но даже использование этих возможностей требует понимания того, что происходит "внутри".

С одной из таких тонкостей использования shell хочется начать, а если статья покажется интересной общественности, то можно будет продолжить другими статьями на эту тематику.

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

ls -lb | while read rights nlink user group other
do echo "$rights for $user:$group"
done

При этом без использования всяких awk и cut в переменную rights попадут права доступа, а в переменные user и group - имя хозяина и группы. А что делать если нужны права только для одного файла и нет необходимости в использовании цикла?

Первым в голову приходит такой вариант решения:

ls -lb -- "$file" | read rights nlink user group other
echo "$rights for $user:$group"

Но выполнении такого скрипта нас будет поджидать неприятность. Переменные окажутся пустыми. Если вывод команды перенаправить во временный файл, и читать значения уже из него, то всё работает так как этого ожидается:

ls -lb -- "$file" > tmpfile
read rights nlink user group other < tmpfile
rm tmpfile
echo "$rights for $user:$group"

Но мы ведь не используем временные файлы?

Можно попытаться достичь нужного результата таким скриптом:

info="`ls -lb -- \"$file\"`"
rights="`echo \"$info\" | awk '{ print $1 }'`"
user="`echo \"$info\" | awk '{ print $3 }'`"
group="`echo \"$info\" | awk '{ print $4 }'`"
echo "$rights for $user:$group"

Но такая конструкция уже лишена изящества да и потребует трёх запусков awk, что точно не ускорит работу скрипта.

Почему же не работает вариант с использованием read и конвеера?

А причина банальна. Как это всем известно в *nix команды в конвеере запускаются параллельно, а это значит, что команда read будет запущена в дочернем процессе и, соответственно, не сможет устанавливать переменные родительского процесса.

Поэтому, для использования read в составе конвеера необходимо ограничивать область видимости прочитанных переменных и работать с ними только в рамках подпроцесса, в котором будет выполняться и сам read:

ls -lb -- "$file" | {
 read rights nlink user group other
 echo "$rights for $user:$group"
}