Я хочу написать сценарий на основе dialog programbox
(или других) и зафиксировать команду stderr
и stdout
, переданную dialog
, в массив (если таковой имеется), а также статус завершения команды (в качестве последнего элемента массива). Для этого я использую подготовленные командные строки command substitution
и fd3 redirection
.
По сути, это работает хорошо, пока нет связанных команд с if
— тогда я получаю сообщения об ошибках, например, в данном случае «команда не найдена». Вот (упрощенный) пример:
# Prepare command strings
declare cmd0='if [[ -e . ]]; then echo TRUE; fi'
cmd1=($("$cmd0") 2>&1)
cmd2=(dialog --colors --no-collapse --programbox "\Zb\Z1List local folders..." 20 160)
# Redirect stdout and stderr to dialog, while executing cmd1 > cmd2, capturing cmd1 exit status
exec 3>&1
tmp=($(${cmd1[@]} 2>&1 | "${cmd2[@]}" 1>&3; echo ${PIPESTATUS[0]}))
exec 3>&-
# Print results
printf '%s\n' "${tmp[@]}"
Кажется, что объявление команды требует объявления массива, но затем используются специальные символы, такие как ';' также добавит проблем. Это то, что я попробовал; прямое использование c
вместо cmd1
приведет к печати всей строки как ошибки.
Что нужно изменить, чтобы command substituion
для связанных команд (включая if
) в cmd1 работал?
Вам нужно $(eval "$cmd0")
Синтаксис оболочки не анализируется после расширения переменных.
Не храните команды в переменных. Переменные предназначены для хранения данных, функции — для хранения команд. Переменные расширяются на полпути в процессе синтаксического анализа команды, поэтому их значения анализируются лишь наполовину как синтаксис оболочки. Если вы используете eval
, они, как правило, подвергаются полуторному анализу, что имеет раздражающую тенденцию нормально работать при тестировании, а затем странным образом терпеть неудачу через некоторое время. Так что просто не делайте этого. См. BashFAQ №50: «Я пытаюсь поместить команду в переменную, но в сложных случаях всегда терпит неудачу!» и множество предыдущих вопросов.
Отвечает ли это на ваш вопрос? Как сохранить команду в переменной в сценарии оболочки?
Спасибо за ваш вклад. Проблема конкретно для dialog programbox
заключается в том, что я хочу отслеживать любой прогресс в реальном времени, не дожидаясь завершения команды(ов). Только тогда общий вывод должен быть сохранен в переменной var, которая в данном случае будет только статусом завершения команды (а не из диалогового окна). Возможно, есть способ использовать tee
, чтобы также захватить данные канала, но я пока не понял, как это сделать. Кроме того, даже если это работает со специальными функциями, это будет многовато только для создания команд dialog
, а с вложенными командами будет довольно сложно.
Хотя некоторые из приведенных выше предложений не полностью «отвечают на мой вопрос», такие как перенос команд для обработки в отдельные function
, были приняты во внимание и применены в моем (окончательном) решении ниже. Другие предложения относительно правильного применения tee
в примерах ниже помогли. В зависимости от варианта использования одно из частичных или альтернативных решений может работать лучше или быть достаточным. Однако окончательное решение, отвечающее всем требованиям, использует всего понемногу, плюс eval
. Не стесняйтесь предлагать улучшения.
С помощью подсказок в комментариях выше я мог бы найти решение, хотя и не совсем удовлетворительное, из-за необходимости определения functions
для каждой сложной/вложенной команды для анализа/передачи dialog programbox
.
dialog_programbox
может принимать и анализировать простые команды через аргумент или, для сложных команд, вместо этого обрабатывать входные данные от stdin
:
Может быть способ извлечь входные данные по конвейеру в захваченную переменную результата (здесь: exitStatus), например. г. за использование tee
— если кто-то знает, как это сделать, пожалуйста, опубликуйте решение.
Для других вариантов dialog
, которые принимают входные данные от переменной и не используют stdin
, выходные данные dialog_programbox
могут быть напечатаны в stdout
как строка, разделенная символом новой строки, со статусом выхода/канала в качестве последнего или первого элемента, для легкого разделения из захваченный результат вар. Я, вероятно, расширю решение соответствующим образом позже.
# Function example for complex commands to pipe to dialog_programbox()
complexCmdFunc1() {
# Requires explicit stderr redirection to stdout for each command
# Could also split cmd execution and store exit status in var for reporting
if ! git worktree remove -- "$1" 2>&1; then
echo -e "\n\nREMOVAL BLOCKED:" "Worktree may not exist or is unlcean/locked (try force) - ABORTING."
# Need to explicitly return exit status to report
return 1
fi
echo -e "Successfully removed worktree:\n$1"
return 0
}
dialog_programbox() {
local -i height
local -i width
local -n cmd
local cmdTmp
local title
for arg; do
if [[ $arg == +([[:digit:]]) ]]; then
if [[ $height -gt 0 ]]; then
width=$arg
continue
fi
height=$arg
elif [[ $title ]]; then
if [[ -R $arg ]]; then
cmd=$arg
# For direct CMD arg injection, has bug
elif ! declare -p "$arg" 2>&1 1>/dev/null; then
cmdTmp=$arg
cmd=cmdTmp
elif [[ $(declare -p "$arg" 2>/dev/null) == 'declare -a'* ]]; then
cmd=$arg
elif [[ -z $cmd ]]; then
cmd=$arg
fi
else
title = "$arg"
fi
done
[[ -z $height ]] && height=30
[[ -z $width ]] && height=120
# CMD ARG INJECTION
if [[ $cmd ]]; then
$( echo "${cmd[@]}" ) 2>&1 | dialog --title "$title" --colors --no-collapse --programbox $height $width 1>&3
echo ${PIPESTATUS[0]}
# stdin command substitution - would work but swallow pipe/exit status
#dialog --title "$title" --colors --no-collapse --programbox $height $width 1>&3 < <($( echo ${cmd[@]} ) 2>&1)
# PIPING STDIN
else
dialog --title "$title" --colors --no-collapse --programbox $height $width </dev/stdin 1>&3
# Temp var would address empty stdin but waste real-time progress report
#local tmp = "$(grep . </dev/stdin)"
# if [[ $tmp ]] ; then
# dialog --title "$title" --colors --no-collapse --programbox $height $width <<<"$tmp"
# else
# echo 'Nothing to process.' | dialog --title "$title" --colors --no-collapse --programbox $height $width
# fi
fi
}
# COMPLEX CMD output piping usage - requires explicit echo of PIPESTATUS
exitStatus1=$(complexCmdFunc1 'unreleased' | dialog_programbox '\Zb\Z1Deleting local worktree...' 20 160; echo ${PIPESTATUS[0]})
# More efficient, since no additional subshell invocation is needed:
complexCmdFunc1 'unreleased' | dialog_programbox '\Zb\Z1Deleting local worktree...' 20 160
exitStatus2=${PIPESTATUS[0]}
# Empty pipe, will render an empty dialog, of which addressing would require storing cmd result in temp var and losing real-time progress reporting
#exitStatus=$(echo | dialog_programbox '\Zb\Z1Deleting local worktree...' 20 160; echo ${PIPESTATUS[0]})
# CMD var injection examples (for simple/non-nested commands)
#declare -a c=(ls foo bar)
declare c='ls foo bar'
exitStatus3=$(dialog_programbox '\Zb\Z1Deleting local worktree...' c 20 160)
# direct CMD injection - HAS BUG
#exitStatus=$(dialog_programbox '\Zb\Z1Deleting local worktree...' 'ls foo bar' 20 160)
echo -e "EXIT_STATUS1: $exitStatus1"
echo -e "EXIT_STATUS2: $exitStatus2"
echo -e "EXIT_STATUS3: $exitStatus3"
Другое решение, основанное на этом ответе Михала Горного (спасибо), пример того, как получить статус выхода И (отображаемый) вывод команды, определенной в функции и обработанной dialog programbox
, с использованием coproc
и глобальных переменных. :
# Result array for storing processed command's output
declare -ga cmdOutput
declare -g exitStatus
worktreeName='unreleased'
# Command to process and get exit status + results from
gitRemoveWtCmd() {
_IFS = "$IFS"; IFS=$'\n'
cmdOutput=( $(git worktree remove -- "$worktreeName" 2>&1) )
exitStatus=$?
IFS = "$_IFS"
if [[ $exitStatus -ne 0 ]]; then
printf '%s\n' "${cmdOutput[@]}"
echo -e "\nCannot remove unclean or non-existent worktree"
else
echo -e "\nSuccessfully removed worktree: [$worktreeName]";
fi
}
# Co-process with dialog to send command results to, redirected output to fd3
coproc diaProgramboxCop {
dialog --colors --no-collapse --programbox '\Zb\Z1List local folders...' 20 160 1>&3
}
# Redirecting processed stderr to output and linking fd3 there
{ exec 3>&1
gitRemoveWtCmd 2>&1
exec 3>&-
} >&${diaProgramboxCop[1]}
exec {diaProgramboxCop[1]}>&-
wait ${diaProgramboxCop_PID}
printf '%s%s\n' "OUTPUT: " "${cmdOutput[@]}"
echo -e "EXIT_STATUS: $exitStatus"
Реальным недостатком здесь является то, что для cmdOutput
и exitStatus
требуются глобальные переменные, а захват вывода команды исключает отображение хода выполнения команды в реальном времени из-за его сохранения через подоболочку в var перед фактической отправкой в dialog
.
Таким образом, этот подход лучше использовать с widgets
, например msgbox
, которые используют предварительно заполненный var
.
Согласно предложению здесь, используя fd3
, перенаправленный из tee + cat
в pipe
, можно использовать для направления всего вывода команды в назначение var (stdout
и stderr
) и вместе с redirection
вывода dialog
непосредственно в /dev/tty
, это другой способ захвата вывода обработанной команды и его отображения через dialog
в режиме реального времени, при условии, что команды могут быть жестко закодированы и даже могут возвращать статус отсутствия прямого канала/выхода:
worktreeName='unreleased'
_IFS = "$IFS"; IFS=$'\n'
cmdOutput=( $({ gitRemoveWtCmd ${worktreeName@Q} 2>&1 | tee >(cat >&3) | dialog_programbox '\Zb\Z1Deleting local worktree...' 20 120 >/dev/tty;} 3>&1 ; echo -e ${PIPESTATUS[0]}) )
printf '%s\n%s\n' "CMD_OUTPUT:" "${cmdOutput[@]:0:((${#cmdOutput[@]} - 1))}"
echo -e "EXIT_STATUS: ${cmdOutput[@]: -1}"
IFS = "$_IFS"
PIPESTATUS
все еще можно распечатать из command substitution
, но его необходимо отделить от захваченного вывода. Но еще лучше просто использовать ;exit ${PIPESTATUS[0]}
из фигурных скобок подстановки того же процесса, обойтись без разделения массива и просто использовать код exit status
из $?
сразу в следующей строке.
Окончательное решение позволяет применять различные команды к dialog
при использовании eval
внутри function getDialogCmdResultAndExitStatus()
, который принимает флаг для отключения любого вывода tty
или stdout
, а также var
или name reference
для команды для обработки и команду dialog
для отображения , при этом по умолчанию все выходные данные команды также передаются в stdout
(для записи в переменную) и всегда возвращаются внутренние pipe status
обработанной команды как exit status
, чтобы включить последующие решения в сценариях:
getDialogCmdResultAndExitStatus() {
local quiet
if [[ $1 =~ ^-[[:alnum:]] ]]; then
if [[ ${1^^} != '-Q' ]]; then
echo -e "Invalild argument: $1"
exit 1
fi
quiet='TRUE'
shift
fi
local -a com
local -n cRef
if ! declare -p "$1" &>/dev/null; then
com = "$1"
else
cRef=$1
com = "${cRef[@]}"
fi
local -a diaCom
local -n diaComRef
if ! declare -p "$2" &>/dev/null; then
diaCom = "$2"
else
diaComRef=$2
diaCom = "${diaComRef[@]}"
fi
if [[ $quiet ]]; then
{ eval "${com[@]@E} 2>&1" | eval "${diaCom[@]@E} >/dev/tty"; return ${PIPESTATUS[0]}; } 3>&1
else
{ eval "${com[@]@E} 2>&1" | tee >(cat - >&3) | eval "${diaCom[@]@E} >/dev/tty"; return ${PIPESTATUS[0]}; } 3>&1
fi
}
Используется следующим образом: использование обработанной команды exit status
(внутренне pipe status
) требует встроенного использования вместе с if
или захвата $?
для дальнейших решений сценария, сразу после строки захвата вывода:
# Prepare commands (utilizing functions from examples above)
declare command = "gitRemoveWtCmd ${worktreeName@Q}"
declare dialogCmd = "dialog_programbox '\Zb\Z1Deleting local worktree...' 20 120"
# Store and limit IFS to newlines
_IFS = "$IFS"; IFS=$'\n'
# Capture command's stdout and stderr + real-time displaying with dialog
cmdOutput=( $(getDialogCmdResultAndExitStatus command dialogCmd) )
# First line after output capturing: get pipe/exit status of processed command
echo -e "EXIT_STATUS: $?"
# Restore IFS to defaults
IFS = "$_IFS"
или
if ! getDialogCmdResultAndExitStatus command dialogCmd; then
# do sth else...
fi
Внутреннюю команду eval
можно еще улучшить, предложения приветствуются.
Вы уверены, что не хотите вместо этого определять функции? (Например,
cmd0 () { if [[ -e . ]]; then echo TRUE; fi; }
.