Захватите вывод команды piped, еще зная, записана ли первая команда в stderr - PullRequest
2 голосов
/ 03 марта 2020

Возможно ли захватить вывод cmd2 из cmd1 | cmd2, все еще зная, записал ли cmd1 в stderr?

Я использую exiftool для вырезать данные exif из файлов:

exiftool "/path/to/file.ext" -all= -o -

Записывает вывод в stdout. Это работает для большинства файлов. Если файл поврежден или не является файлом видео / изображения, он ничего не записывает в stdout и вместо этого записывает ошибку в stderr. Например:

Error: Writing of this type of file is not supported - /path/to/file.ext

В конечном итоге мне нужно захватить md5 файлов, которые не приводят к ошибке. Прямо сейчас я делаю это:

md5=$(exiftool "/path/to/file.ext" -all= -o - | md5sum | awk '{print $1}')

Независимо от того, является ли файл изображением / видео, он вычислит md5.

Если файл является изображением / видео, он ' захватит md5 файла, как и ожидалось.

Если файл не изображения / видео, exiftool ничего не записывает в stdout, и поэтому md5sum вычисляет md5 нулевого ввода. Но эта строка также напишет ошибку в stderr.

Мне нужно иметь возможность проверить, было ли что-то записано в stderr, поэтому я знаю, что нужно отбросить вычисленный md5.

I Я знаю, что одна альтернатива - запустить exiftool дважды: один раз без md5sum и без захвата, чтобы увидеть, было ли что-то записано в stderr, а затем второй раз с md5sum и захватом. Но это значит, что я должен запустить exiftool дважды. Я хочу избежать этого, потому что это может занять много времени для больших файлов. Я бы предпочел запустить его только один раз.

Обновление

Кроме того, я не могу зафиксировать вывод только exiftool, потому что он выдает эту ошибку:

bash: warning: command substitution: ignored null byte in input

И я не могу игнорировать эту ошибку, потому что результат md5 не совпадает. То есть:

file=$(exiftool "/path/to/file.ext" -all= -o -)
echo "$file" | md5sum

Распечатает вышеуказанную ошибку нулевого байта и не будет иметь такой же результат md5, как:

exiftool "/path/to/file.ext" -all= -o - | md5sum

Ответы [ 4 ]

2 голосов
/ 03 марта 2020

Для этого можно использовать сопроцесс:

#!/usr/bin/env bash
case $BASH_VERSION in [0-3].*) echo "ERROR: Bash 4+ required" >&2; exit 1;; esac

coproc STDERR_CHECK { seen=0; while IFS= read -r; do seen=1; done; echo "$seen"; }
{
  md5=$(exiftool "/path/to/file.ext" -all= -o - | md5sum | awk '{print $1}')
} 2>&${STDERR_CHECK[1]}
exec {STDERR_CHECK[1]}>&-
read stderr_seen <&"${STDERR_CHECK[0]}"

if (( stderr_seen )); then
  echo "exiftool emitted stdout with md5 $md5, and had content on stderr"
else
  echo "exiftool emitted stdout with md5 $md5, and did not emit any content on stderr"
fi
2 голосов
/ 03 марта 2020

md5=$(exec 3>&1; (exiftool "/path/to/file.ext" -all= -o - 2>&1 1>&3) 3> >(md5sum | awk '{print $1}' >&3) | grep -q .)

Это открывает дескриптор файла 3 и перенаправляет его в дескриптор файла 1 (он же stdout).

Хитрость заключается в перенаправлении exiftool выходных данных:

  • exiftool ... 2>&1 сообщает, что файловый дескриптор 2 (он же stderr) перенаправлен на стандартный вывод
  • exiftool ... 1>&3 сообщает, что стандартный вывод перенаправлен на файловый дескриптор 3, который в данный момент перенаправляется на стандартный вывод

Затем fd 3 перенаправляется в другую цепочку команд, используя подстановку процесса, т.е. 3> >(md5sum | awk '{print $1}' >&3), где 3> указывает на перенаправление fd3, а >(...) - это сама подстановка процесса.

At в то же время стандартная ошибка exiftool записывается в стандартный вывод, который передается в grep -q ., который возвращает 0, если есть хотя бы один символ.

Поскольку grep -q . является последней командой выполняется в основной цепочке команд, вы можете просто проверить результаты $?:

md5=$(exec 3>&1; (exiftool "/path/to/file.ext" -all= -o - 2>&1 1>&3) 3> >(md5sum | awk '{print $1}' >&3) | grep -q .)
if [ $? -eq 0 ]
then
  # something was written to exiftool's stderr
fi

Ошибка не будет записана. Если вы хотите увидеть ошибку, но не зафиксировать ее в md5, замените grep -q . на grep . >&2

md5=$(exec 3>&1; (exiftool "/path/to/file.ext" -all= -o - 2>&1 1>&3) 3> >(md5sum | awk '{print $1}' >&3) | grep . >&2)

Очень важно, чтобы вы перенаправили exiftool выходы в этом самом порядке. Если вы перенаправили следующим образом:

exiftool "/path/to/file.ext" -all= -o - 1>&3 2>&1

Затем stdout перенаправляется на fd3, а затем stderr перенаправляется на stdout. Но поскольку 1>&3 происходит до 2>&1, то stderr будет перенаправлен на стандартный вывод, который в данный момент перенаправляется на fd3. Вы определенно не хотите этого.

Конец цепочки замещения процесса записывает в fd3 с >&3, потому что вы хотите сохранить результат в fd3. Без >&3 результат awk окажется в fd1, который будет передан в grep -q . или grep . >&2, и, опять же, вы определенно не хотите этого.

PS. вам не нужно закрывать fd3, потому что он был открыт во время подпроцесса при назначении md5. Если вам нужно закрыть дескриптор файла, пожалуйста, позвоните exec 3>&-

2 голосов
/ 03 марта 2020

Для этого существует специальный var (массив) PIPESTATUS, простой пример, файл и файл2 существуют

$ ls file &> /dev/null | ls file2 &> /dev/null; echo ${PIPESTATUS[@]}
0 0

А здесь файл3 не существует

$ ls file3 &> /dev/null | ls file2 &> /dev/null; echo ${PIPESTATUS[@]}
2 0

$ ls file3; echo $?
ls: cannot access 'file3': No such file or directory
2

Тройная труба

$ ls file 2> /dev/null | ls file3 &> /dev/null | ls file2 &> /dev/null; echo ${PIPESTATUS[@]}
0 2 0

Труба в var проверена с grep

$ test=$(ls file | grep .; ((${PIPESTATUS[1]} > 0)) && echo error)
$ echo $test
file

$ test=$(ls file3 | grep .; ((${PIPESTATUS[1]} > 0)) && echo error)
ls: cannot access 'file3': No such file or directory
$ echo $test
error

Другой подход заключается в проверке, что тип файла - это изображение или видео.

type=$(file "/path/to/file.ext")
case $type in
    *image*|*Media*) echo "is an image or video";;
esac
0 голосов
/ 03 марта 2020

Просто захватите вывод, а затем условно запишите его. Например:

if out="$(exiftool "/path/to/file.ext" -all= -o - )"; then
    md5=$(echo "$out" | md5sum | awk '{print $1}'))
fi

Это присваивает значение md5 и возвращает статус выхода exiftool, который проверяется if. Обратите внимание, что эта конструкция предполагает, что exiftool возвращает разумное значение выхода.

...