Перехват и сбор выходных данных скрипта, ошибка «входной файл – выходной файл»?

Мне нужно загрузить вывод текущего скрипта, поэтому я добавил trap и set -ex , например

 #!/bin/bash exec &> /tmp/error.log trap 'cat /tmp/error.log; curl http://127.0.0.1/error.php?hostname=$(hostname) -F file=@/tmp/error.log' EXIT set -ex wtfwtf 

Когда я его выполняю, я всегда получаю эту ошибку, а скрипт PHP не получает весь файл

 %> cat /tmp/error.log 1.sh: line 6: wtfwtf: command not found cat: /tmp/error.log: input file is output file 

Пока единственное решение – скопировать error.log в новый файл и загрузить его, например,

 #!/bin/bash exec &> /tmp/error.log trap 'cp /tmp/error.log 123; curl http://127.0.0.1/error.php?hostname=$(hostname) -F file=@123' EXIT set -ex wtfwtf 

Есть ли лучший способ сделать это?

    С помощью exec вы перенаправляете весь вывод скрипта в определенный файл журнала.

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

    BSD cat не выполняет ту же проверку, что и GNU cat , что, если сценарий не прерывается, приводит к созданию бесконечно большого файла журнала с одними и теми же несколькими строками, повторяющимися снова и снова.

    Обходной путь – сохранить исходный стандартный дескриптор выходного файла, выполнить redirect, как раньше, а затем восстановить его в ловушке.

     #!/bin/bash exec 3>&1 # make fd 3 copy of original fd 1 exec >/tmp/error.log 2>&1 # in the trap, make fd 1 copy of fd 3 and close fd 3 (ie move fd 3 to fd 1) trap 'exec 1>&3-; cat /tmp/error.log; curl "http://127.0.0.1/error.php?hostname=$(hostname)" -F file=@/tmp/error.log' EXIT set -ex wtfwtf 

    Это делает копию файлового дескриптора 1 (как fd 3) перед redirectм его в файл журнала. В ловушке мы перемещаем эту копию обратно в fd 1 и делаем вывод.

    Обратите внимание, что стандартный stream ошибок в ловушке в этом примере все еще подключен к файлу журнала. Поэтому, если curl генерирует диагностическое сообщение, оно будет сохранено в файле журнала, а не будет отображаться на терминале (или там, где был подключен исходный стандартный stream ошибок).


    Принимая во внимание комментарий Стефана Шазеля :

     #!/bin/sh exit_handler () { # 1. Make standard output be the original standard error # (by using fd 3, which is a copy of original fd 2) # 2. Do the same with standard error # 3. Close fd 3. exec >&3 2>&3 3>&- cat "$logfile" curl "some URL" -F "file=@$logfile" } logfile='/var/log/myscript.log' # Truncate the logfile. : >"$logfile" # 1. Make fd 3 a copy of standard error (fd 2) # 2. Redirect original standard output to the logfile (appending) # 3. Redirect original standard error to the logfile (will also append) exec 3>&2 >>"$logfile" 2>&1 # Use shell function for exit trap (for neatness) trap exit_handler EXIT set -ex wtfwtf 

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

    Он также указывает, что опасно использовать фиксированное имя файла в общедоступном каталоге, таком как /tmp . Это связано с тем, что в сценарии не установлена ​​никакая проверка, чтобы убедиться, что этот файл еще не существует (кто-то или некоторые вредоносные программы могли создать символическую ссылку /tmp/error.log на /etc/passwd или ваш ~/.bashrc для пример). Его решение заключается в том, чтобы вместо этого использовать выделенный файл постоянного журнала для сценария в /var/log (файл является постоянным, но содержимое будет очищено при запуске сценария).

    Вариантом этого будет использование mktemp для создания уникального имени файла в $TMPDIR (и затем удаление этого файла в ловушке EXIT , если только curl не завершится, в этом случае rm не будет выполняться, так как set -e ):

     #!/bin/sh exit_handler () { # 1. Make standard output be the original standard error # (by using fd 3, which is a copy of original fd 2) # 2. Do the same with standard error # 3. Close fd 3. exec >&3 2>&3 3>&- cat "$logfile" curl "some URL" -F "file=@$logfile" rm -f "$logfile" } logfile=$( mktemp ) # 1. Make fd 3 a copy of standard error (fd 2) # 2. Redirect original standard output to the logfile (appending) # 3. Redirect original standard error to the logfile (will also append) exec 3>&2 >>"$logfile" 2>&1 # Use shell function for exit trap (for neatness) trap exit_handler EXIT set -ex wtfwtf 

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


    Незначительные мелочи: URL-адреса в командной строке, вероятно, всегда должны заключаться в двойные кавычки, так как они обычно содержат символы, которые shell может интерпретировать как специальные (например ? ).