Захватите поток ошибок строки кода и сообщите об этом с помощью trap, сохраняя при этом нетронутым стандартный вывод.

У меня есть следующие (упрощенные) попытки кода в двух сценариях оболочки ниже.

Сценарий вызывает сценарий R, в котором запускается код, который генерирует стандартный вывод и поток ошибок в зависимости от того, что происходит.

Я пытаюсь добиться того, чтобы как вывод, так и поток ошибок отображались на консоли, как обычно, но когда сценарий запускается и происходит сбой (например, RScript создает поток ошибок), я хочу сохранить это сообщение об ошибке, которое генерирует R в базе данных sqlite, но по-прежнему имеет нормальный вывод и ошибки, отображаемые на консоли, а также при выходе. Я пробовал много форм перенаправления вывода функции Rscript, но либо ничего не сохранял в базе данных (кроме номера строки), либо я мог сохранить сообщение об ошибке, но ничего не было помещено в консоль...

Это не сохранит сообщение об ошибке в БД (номер строки сохранится), но все будет на консоли

updateDBwhenError() {
    sqlite3 "myDB.db" "INSERT INTO logs VALUES('$1')"   
}

err_report() {
    #Tryign to capture both line error and message
    updateDBwhenError "Error Line $1 $2"
    exit
}
trap 'err_report ${LINENO}' ERR

Rscript testScript.R

Это сохранит сообщение об ошибке в БД, но на консоли больше ничего не будет.

updateDBwhenError() {
    sqlite3 "myDB.db" "INSERT INTO logs VALUES('$1')"   
}

err_report() {
    #Tryign to capture both line error and message
    updateDBwhenError "Error Line $1 $rErr"
    exit
}
trap 'err_report ${LINENO}' ERR

rErr=$(Rscript testScript.R 2>&1)

Я везде искал способ записать только поток ошибок в переменную и сохранить вывод нетронутым на консоли (как вывод, так и ошибку), но я застрял :)

Грц, П.Дж.


person pieterjanvc    schedule 17.09.2020    source источник
comment
Это Как я могу захватить STDERR в bash переменная и не влияет на STDOUT? не отвечает на вопрос.   -  person Ivan    schedule 18.09.2020
comment
@Barmar, не могли бы вы снова открыть этот вопрос, потому что заостренный ответ не отвечает на него   -  person Ivan    schedule 18.09.2020


Ответы (2)


Попробуй это

exec 5>&1
exec 6>&1
rErr=$(Rscript testScript.R 2>&1 1>&6 | tee /dev/fd/5)

Тестовый скрипт

$ cat test
#!/bin/bash
ls
ls sdfgds

Тестирование с помощью этого скрипта

$ exec 5>&1
$ exec 6>&1
$ err=$(./test 2>&1 1>&6 | tee /dev/fd/5)
file  new_file  test  xml
ls: cannot access 'sdfgds': No such file or directory

$ echo "$err"
ls: cannot access 'sdfgds': No such file or directory
person Ivan    schedule 18.09.2020
comment
OP хочет сохранить вывод на консоль - person Ivan; 18.09.2020
comment
Тестировал так exec 5>&1; err=$(ls sadfasf 2>&1 1>&5) ничего на консоли, только в var - person Ivan; 18.09.2020
comment
Привет. Я согласен с @Ivan, что этот не работает. Помните, я хочу только захватить поток ошибок из RScript в var, но иметь как ошибку, так и вывод на консоли. - person pieterjanvc; 18.09.2020
comment
Конечно, вы правы. tee нужен для дублирования потока. - person KamilCuk; 18.09.2020
comment
Обновлен мой ответ - person Ivan; 18.09.2020

Вскоре

Использование безымянных файлов FIFO (ВНИМАНИЕ, это будет работать, потому что ОС выполняет буферизацию, поэтому только до тех пор, пока выходные данные не превышают 64 КБ!), есть тонкий bash следующим образом:

exec {HOLDERR}<> <(:)
ls -ld /t{mp,nt} 2>&${HOLDERR}
read -t 0 -u $HOLDERR && read -ru $HOLDERR errmsg
exec {HOLDERR}<&-

Это должно вывести что-то вроде:

drwxrwxrwt 4 root root 4096 Jan 01 1970 /tmp

и заполните $errmsg чем-то вроде: declare -p errmsg

declare -a errmsg=([0]="ls: cannot access '/tnt': No such file or directory")

На шаг впереди

exec {HOLDERR}<> <(:)
ls -ld /t{mp,nt} 2>&${HOLDERR}
errmsg=()
while read -t 0 -u $HOLDERR;do
    read -ru $HOLDERR line
    errmsg+=("$line")
done
exec {HOLDERR}<&-
printf "%s\n" "${errmsg[@]}"

Полная рабочая версия

С trap SIGCHLD для сброса fifo.

printmsg() {
    local out=()
    out=("${errmsg[@]}")
    errmsg=("${errmsg[@]:${#out[@]}}")
    [ "${#out[@]}" -gt 0 ] &&
    printf "Err: %s\n" "${out[@]}"
}
checkmsg() {
    local line
    while read -u $HOLDERR -t 0 ;do
    read -ru $HOLDERR line &&
        errmsg+=("$line")
    done
}
msg=()
trap checkmsg CHLD
exec {HOLDERR}<> <(:)

затем

ls -ld /t{mp,nt} 2>&${HOLDERR}

должен вывести что-то вроде:

drwxrwxrwt 4 root root 4096 Jan 01 1970 /tmp

с последующим:

printmsg

покажет

Err: ls: cannot access '/tnt': No such file or directory
person F. Hauri    schedule 18.09.2020
comment
exec {HOLDERR}<> <(:) Стоит отметить, что это зависит от буферизации fifo и может блокироваться. Fifo имеет собственную буферизацию, равную 65536 байт (как я читаю man-страница), потенциально он может заблокироваться, если процесс запишет больше байтов, что заблокирует ваш скрипт. Решением здесь является многопроцессорность — запуск ls в качестве фонового процесса и чтение вывода во время wait работы над ним. - person KamilCuk; 18.09.2020
comment
@KamilCuk Ты прав! Ответ отредактирован! Но, конечно, это может блокировать, но не с разумным выходом STDERR (меньше 64k;). - person F. Hauri; 18.09.2020
comment
Привет. Спасибо за ответ! Я посмотрю на это, но я признаю, что оно выходит далеко за рамки моего текущего понимания, поэтому мне придется поработать над этим, чтобы увидеть, смогу ли я реализовать и работать :) Я думал, что просто упускаю что-то простое, но кажется, если Я действительно хочу осуществить это, мне понадобится намного более сложный код... Я буду держать вас в курсе - person pieterjanvc; 18.09.2020