Как подавить ошибки, связанные с отсутствующим файлом в цикле while - PullRequest
1 голос
/ 11 февраля 2020

Например, вот часть моего bash сценария:

while read line
do
    [some stuff]
done < $group.$project.$branch.notready.txt

Но в течение l oop, находится ли этот или этот l oop в другом l oop, имя файла может измениться с не готового на готовое. Когда это происходит, я не получаю такую ​​ошибку файла или каталога. Это нормально, если файл не существует для сценария, но есть ли способ подавить ошибку?

Я пробовал:

while read line 2>/dev/null

, но это не так работа.

Редактировать: это может быть более простой способ воспроизвести

while read line
do
    echo "$line"
done < random.file.dont.exist.txt

Ответы [ 4 ]

3 голосов
/ 11 февраля 2020

Подумайте о том, чтобы отделить операцию открытия от самой l oop, чтобы вы могли обрабатывать ее ошибки отдельно:

#!/usr/bin/env bash
case $BASH_VERSION in ''|[1-3].*|4.[012].*) echo "ERROR: Bash 4.3+ needed" >&2; exit 1;; esac
if { exec {input_fd}<possibly_existing_file; } 2>/dev/null; then
  while IFS= read -r line <&$input_fd; do
    echo "Read line $line from possibly_existing_file"
  done
  exec {input_fd}<&- # close input file
else
  echo "possibly_existing_file does not exist yet or otherwise could not be opened" >&2
fi

exec {input_fd}<filename - это bash 4,3 automati c присваивание дескриптора файла поддержка, которая выбирает номер дескриптора открытого файла и помещает его в переменную input_fd. В оболочках, достаточно новых для такой поддержки, вы также можете использовать exec {input_fd}<&- для закрытия файла или <&$fd_var для перенаправления на номер FD, найденный в переменной.

1 голос
/ 12 февраля 2020

Существует еще один вопрос к этому вопросу, который бросается в глаза:

ОП гласит: Но в течение всего 1 oop, этот ли или это l oop находится внутри другого l oop, имя файла может измениться с неготового на готовое. Когда это происходит, я не получаю такую ​​ошибку файла или каталога.

Здесь необходимо задать следующие вопросы:

  1. Каково происхождение ошибки? Создание перенаправления ввода с несуществующим файлом.
  2. Что произойдет с l oop, если файл будет переименован или удален? Ничего, это не влияет на l oop.
  3. Как я могу решить эту проблему? С проверкой на достоверность (см. Ниже) .

1. Источник ошибки:

Ошибка, с которой сталкиваются в OP, является ошибкой, созданной самой bash при попытке создать перенаправление ввода с несуществующим файлом. Это не имеет никакого отношения к while-l oop, представленному в ОП.

$ cat - < "$(mktemp -u)"
bash: /tmp/tmp.qynPrg8Mst: No such file or directory

2. Что происходит с while-l oop, если ввод удаляется / переименовывается?

Предполагается следующее выполнение:

#!/usr/bin/env bash
fname="$(mktemp)"
seq 1 2 > "$fname"
while read -r line; do
   echo "$line"
   [ -r "$fname" ] && rm "$fname" && ls -l /proc/$$/fd
done < "$fname"

Этот процесс:

  1. создает временный файл, содержащий 2 строки
  2. использует время l oop для чтения файла строка за строкой
  3. на первом цикле l oop, удаляет ввод файл и печатает файловые дескрипторы запущенного процесса
$ ./test.sh 
1
total 0
lr-x------ 1 user group 64 Feb 12 14:52 0 -> /tmp/tmp.6oTtotu6Xr (deleted)
l-wx------ 1 user group 64 Feb 12 14:52 1 -> /dev/pts/46
lrwx------ 1 user group 64 Feb 12 14:52 10 -> /dev/pts/46
lrwx------ 1 user group 64 Feb 12 14:52 2 -> /dev/pts/46
lr-x------ 1 user group 64 Feb 12 14:52 255 -> /home/user/tmp/test.sh
2

Что вы заметили, это то, что, хотя файл удаляется во втором цикле, он все равно может прочитать его. С другой стороны, соответствующий файловый дескриптор (0) все еще существует, но помечен как удаленный. Причина этого указана в [ServerFault]. Что происходит с удаленным файлом, все еще подлежащим перенаправлению на linux?

Если в программе по-прежнему открыт дескриптор файла, файл продолжает существовать. Он исчезает из списка ваших каталогов и выглядит пропавшим, но до тех пор, пока последний дескриптор файла не будет закрыт, файл продолжает расти.

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

#!/usr/bin/env bash
fname="$(mktemp)"
fname2="$(mktemp)"
echo "$fname $fname2"
seq 1 2 > "$fname"
while read -r line; do
   echo "$line"
   [ -r "$fname" ] && mv "$fname" "$fname2" && ls -l /proc/$$/fd
done < "$fname"

, который выводит:

/tmp/tmp.3AYt84IJDW /tmp/tmp.JQJGAsUuBz
1
total 0
lr-x------ 1 user group 64 Feb 12 15:01 0 -> /tmp/tmp.JQJGAsUuBz
l-wx------ 1 user group 64 Feb 12 15:01 1 -> /dev/pts/46
lrwx------ 1 user group 64 Feb 12 15:01 10 -> /dev/pts/46
lrwx------ 1 user group 64 Feb 12 15:01 2 -> /dev/pts/46
lr-x------ 1 user group 64 Feb 12 15:01 255 -> /home/user/tmp/test.sh
2

3 , Что это значит для OP?

Два приведенных выше случая демонстрируют, что удаление или переименование файла, который используется в качестве стандартного в процессе, в данном случае while-l oop, не влияет на правильное исполнение л oop. Ошибка возникает только при создании перенаправления ввода с несуществующим файлом. Возможный способ для OP:

#!/usr/bin/env bash
fname="/path/to/input/"
while read -r line
    echo "$line"
done < "$( [ -r "$fname" ] && echo "$fname" || echo "/dev/null" )"

Это, однако, все еще делает возможным, но менее вероятно, что входной файл $fname изменяется во время создания перенаправления ввода.

Самый безопасный способ был бы:

#!/usr/bin/env bash
fname="/path/to/input"
{ exec 3< "$fname"; } 2> /dev/null || { exec 3< /dev/null; }
while read -r -u 3 line
    echo "$line"
done
exec 3>&-

Выше по существу задает дескриптор файла 3 для входного файла, но если он не существует, указывает на /dev/null. Затем он выполняет l oop с /dev/null

Когда у вас, как упомянуто в ОП, есть этот l oop в другой l oop, вы можете сделать:

#!/usr/bin/env bash
fname="/path/to/input"
for i in 1 2; do
   { exec 3< "$fname"; } 2> /dev/null || break
   while read -r -u 3 line
      echo "$line"
   done
   exec 3>&-
done

Полезные ссылки:

https://wiki.bash-hackers.org/howto/redirection_tutorial

1 голос
/ 11 февраля 2020

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

exec 3<"$file" &&
while read line
do echo "$line"
done <&3
exec 3<&-

Само по себе, что до сих пор не ' подавить сообщение, но мы можем перенаправить стандартную ошибку, пока мы exec, и восстановить ее после. Это выглядит немного грязно, но делает работу:

exec 4>&2 2>/dev/null 3<"$file" 2>&4 4>&- &&
while read line <&3
do
    echo "$line"
done
exec 3<&-

Разбитая строка exec:

  1. Дублирует стандартную ошибку в поток 4, сохраняя ее для дальнейшего использования.
  2. Отправляет стандартную ошибку на нулевое устройство.
  3. Открывает наш входной файл по дескриптору 3.
  4. Восстанавливает стандартную ошибку из 4, где мы ее сохранили.
  5. Закрывает неиспользуемый поток 4.

Если эти шаги выполнены успешно, мы выполняем while l oop (и обычные потоки восстанавливаются, поэтому ошибки в l oop будут go к потоку ошибок как обычно).

Мы должны перепрыгнуть через h oop сохранения и восстановления потока ошибок, потому что нет способа перенаправить вывод exec - его изменения постоянны .

Приложение

Как показано в другом ответе , мы можем перенаправить только сообщение об ошибке, не перепрыгивая через обручи (и пропускать закрытие, если открытие не удается):

{ exec 3<"$file"; } 2>/dev/null &&
{
    while read line <&3
    do
        echo "$line" >&2
    done
    exec 3<&-
}
1 голос
/ 11 февраля 2020

Если вы заключите весь оператор while в фигурные скобки, он сработает, но учтите, что он подавит любые сообщения об ошибках из любой части l oop:

{
    while read line; do
        echo "$line"
    done < random.file.dont.exist.txt
} 2>/dev/null
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...