Подтвердить что ты не робот

Как сохранить вывод команды в переменной без создания подоболочки [Bash <v4]

ksh имеет действительно интересную конструкцию, подробно описанную в этом ответе: qaru.site/info/4703/...

Так как Bash 4.0, встроенная команда mapfile встроенная команда должна решить эту проблему: http://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html

Но, как ни странно, он не работает с заменой процесса:

foo () { echo ${BASH_SUBSHELL}; }
mapfile -t foo_output <(foo) # FAIL: hang forever here
subshell_depth=${foo_output[0]} # should be 0

Но как это сделать в Bash v3.2?

4b9b3361

Ответ 1

Вот еще один способ сделать это, который достаточно отличается, что требует отдельного ответа. Я думаю, что этот метод не содержит вложенных оболочек и подпроцесса bash:

[email protected]:~$ bar () { echo "$BASH_SUBSHELL $BASHPID"; }
[email protected]:~$ bar
0 8215
[email protected]:~$ mkfifo /tmp/myfifo
[email protected]:~$ exec 3<> /tmp/myfifo
[email protected]:~$ unlink /tmp/myfifo
[email protected]:~$ bar 1>&3
[email protected]:~$ read -u3 a
[email protected]:~$ echo $a
0 8215
[email protected]:~$ exec 3>&-
[email protected]:~$

Хитрость здесь заключается в том, чтобы использовать exec, чтобы открыть FIFO в режиме чтения-записи с помощью FD, что, похоже, имеет побочный эффект, заключающийся в том, что FIFO не блокируется. Затем вы можете перенаправить свою команду на FD без блокировки, а затем прочитать FD.

Обратите внимание, что FIFO будет буфером ограниченного размера, вероятно, около 4 КБ, поэтому, если ваша команда выдает больше выходных данных, чем это, она снова блокируется.

Ответ 2

Вот что я мог бы придумать - его немного беспорядочно, но foo запускается в контексте оболочки верхнего уровня, а его вывод предоставляется в переменной a в контексте оболочки верхнего уровня:

#!/bin/bash

foo () { echo ${BASH_SUBSHELL}; }

mkfifo /tmp/fifo{1,2}
{
    # block, then read everything in fifo1 into the buffer array
    i=0
    while IFS='' read -r ln; do
        buf[$((i++))]="$ln"
    done < /tmp/fifo1
    # then write everything in the buffer array to fifo2
    for i in ${!buf[@]}; do
        printf "%s\n" "${buf[$i]}"
    done > /tmp/fifo2
} &

foo > /tmp/fifo1
read a < /tmp/fifo2
echo $a

rm /tmp/fifo{1,2}

Это, конечно, предполагает две вещи:

  • fifos разрешены
  • Командная группа, выполняющая буферизацию, может быть помещена в фоновый рисунок

Я тестировал это, чтобы работать в этих версии:

  • 3.00.15 (1) -release (x86_64-redhat-linux-gnu)
  • 3.2.48 (1) -release (x86_64-apple-darwin12)
  • 4.2.25 (1) -release (x86_64-pc-linux-gnu)

Добавление

Я не уверен, что подход mapfile в bash 4.x делает то, что вы хотите, поскольку подстановка процесса <() создает целый новый процесс bash (хотя и не bash подоболочка в пределах этого bash):

$ bar () { echo "$BASH_SUBSHELL $BASHPID"; }
$ bar
0 2636
$ mapfile -t bar_output < <(bar)
$ echo ${bar_output[0]}
0 60780
$ 

Таким образом, хотя $BASH_SUBSHELL здесь 0, это потому, что он находится на верхнем уровне нового процесса 60780 оболочки в процессе замены.

Ответ 3

Самый простой способ - отбросить функцию и передать переменную напрямую, например:

declare -a foo_output
mapfile -t foo_output <<<${BASH_SUBSHELL}
subshell_depth=${foo_output[0]} # Should be zero.

В противном случае заданы два элемента в функции:

foo () { echo "$BASH_SUBSHELL $BASHPID"; }

вы можете использовать read (при необходимости изменить IFS) как одну из следующих команд:

cat < <(foo) | read subshell_depth pid # Two variables.
read -r subshell_depth pid < <(foo) # Two separate variables.
read -a -r foo_arr < <(foo) # One array.

или используя readarray/mapfile (Bash > 4):

mapfile -t foo_output < <(foo)
readarray -t foo_output < <(foo)

затем преобразует вывод обратно в массив:

foo_arr=($foo_output)
subshell_depth=${foo_arr[0]} # should be 0

Ответ 4

Этот вопрос возникает очень часто при поиске, как просто записать вывод любой "печатной" команды в переменную. Так что для тех, кто ищет это возможно (начиная с bash v3.1.0) с помощью:

printf -v VARIABLE_NAME "whatever you need here: %s" $ID

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