
Как сохранить вывод команды, изменяющей среду, в переменную?
Я использую оболочку bash.
Предположим, что у меня есть:
function f () { a=3; b=4 ; echo "`date`: $a $b"; }
И теперь я могу использовать команды для запуска f
:
$ a=0; b=0; f; echo $a; echo $b; echo $c
Sat Jun 28 21:27:08 CEST 2014: 3 4
3
4
но я хотел бы сохранить вывод в f
переменную c
, поэтому я попробовал:
a=0; b=0; c=""; c=$(f); echo $a; echo $b; echo $c
но к сожалению, у меня есть:
0
0
Sat Jun 28 21:28:03 CEST 2014: 3 4
поэтому у меня здесь нет никаких изменений в обстановке.
Как сохранить вывод команды (не только функции) в переменную и сохранить изменения среды?
Я знаю, что $(...)
открывается новая подоболочка, и в этом проблема, но можно ли как-то обойти это?
решение1
Если вы используете Bash 4 или более позднюю версию, вы можете использоватьсопроцессы:
function f () { a=3; b=4 ; echo "`date`: $a $b"; }
coproc cat
f >&${COPROC[1]}
exec {COPROC[1]}>&-
read c <&${COPROC[0]}
echo a $a
echo b $b
echo c $c
выведет
a 3
b 4
c Sun Jun 29 10:08:15 NZST 2014: 3 4
coproc
создает новый процесс, выполняющий заданную команду (здесь, cat
). Он сохраняет PID в COPROC_PID
и стандартные дескрипторы файлов вывода/ввода в массив COPROC
(точно так же, какpipe(2)
, или см.здесьилиздесь).
Здесь мы запускаем функцию со стандартным выводом, направленным на наш запущенный сопроцесс cat
, а затемread
из него. Так как cat
просто выплевывает свои входные данные обратно, мы получаем вывод функции в нашу переменную. exec {COPROC[1]}>&-
просто закрывает дескриптор файла, чтобы cat
не ждать вечно.
Обратите внимание, что это read
занимает только одну строку за раз. Вы можете использовать mapfile
для получения массива строк или просто использовать дескриптор файла, как бы вы ни хотели его использовать другим способом.
exec {COPROC[1]}>&-
работает в текущих версиях Bash, но более ранние версии 4-й серии требуют сначала сохранить дескриптор файла в простую переменную: fd=${COPROC[1]}; exec {fd}>&-
. Если ваша переменная не установлена, стандартный вывод будет закрыт.
Если вы используете версию Bash 3-й серии, вы можете получить тот же эффект с помощьюmkfifo
, но на данном этапе это не намного лучше, чем использование реального файла.
решение2
Если вы уже рассчитываете на то, что тело будет f
выполняться в той же оболочке, что и вызывающая сторона, и, таким образом, сможете изменять переменные, такие как a
и b
, почему бы не сделать функцию просто установленной c
? Другими словами:
$ function f () { a=3; b=4 ; c=$(echo "$(date): $a $b"); }
$ a=0; b=0; f; echo $a; echo $b; echo $c
3
4
Mon Jun 30 14:32:00 EDT 2014: 3 4
Одной из возможных причин может быть то, что выходная переменная должна иметь разные имена (c1, c2 и т. д.) при вызове в разных местах, но вы можете решить эту проблему, задав функции значение c_TEMP и заставив вызывающие функции делать то же самое c1=$c_TEMP
и т. д.
решение3
Это клудж, но попробуй
f > c.file
c=$(cat c.file)
(и, по желанию, rm c.file
).
решение4
Просто назначьте все переменные и запишите вывод одновременно.
f() { c= ; echo "${c:=$(date): $((a=3)) $((b=4))}" ; }
А теперь, если вы это сделаете:
f ; echo "$a $b $c"
Ваш вывод:
Tue Jul 1 04:58:17 PDT 2014: 3 4
3 4 Tue Jul 1 04:58:17 PDT 2014: 3 4
Обратите внимание, что это полностью переносимый код POSIX. Я изначально установил c=
пустую ''
строку, поскольку расширение параметра ограничивает одновременное назначение переменных + оценку только числовыми(нравиться $((var=num))
)или из нулевых или несуществующих значений - или, другими словами, вы не можете одновременно установитьивычислить переменную в произвольную строку, если этой переменной уже присвоено значение. Поэтому я просто проверяю, что она пустая, прежде чем пытаться. Если бы я не опустошил ее c
перед попыткой присвоить ей значение, расширение вернуло бы только старое значение.
Просто для демонстрации:
sh -c '
c=oldval a=1
echo ${c:=newval} $((a=a+a))
'
###OUTPUT###
oldval 2
newval
являетсянетприсваивается $c
inline, поскольку oldval
расширяется до ${word}
, тогда как inline $((
арифметическое =
присваивание ))
происходит всегда. Но если$c
не имеет oldval
и пусто или не установлено...
sh -c '
c=oldval a=1
echo ${c:=newval} $((a=a+a))
c= a=$((a+a))
echo ${c:=newval} $((a=a+a))
'
###OUTPUT###
oldval 2
newval 8
...тогда newval
сразу же присваивается и расширяется до $c
.
Все остальные способы сделать это подразумевают некоторую форму вторичной оценки. Например, предположим, что я хочу присвоить вывод f()
переменной, названной name
в одной точке и var
в другой. В текущей версии это не будет работать без установки var в области видимости вызывающего. Однако другой способ может выглядеть так:
f(){ fout_name= fout= set -- "${1##[0-9]*}" "${1%%*[![:alnum:]]*}"
(: ${2:?invalid or unspecified param - name set to fout}) || set --
export "${fout_name:=${1:-fout}}=${fout:=$(date): $((a=${a:-50}+1)) $((b=${b:-100}-4))}"
printf %s\\n "$fout"
}
f &&
printf %s\\n \
"$fout_name" \
"$fout" \
"$a" "$b"
Ниже я привел пример с лучшим форматированием, но, если назвать его так, как указано выше, вывод будет следующим:
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul 2 02:27:07 PDT 2014: 51 96
fout
Wed Jul 2 02:27:07 PDT 2014: 51 96
51
96
Или с другими $ENV
аргументами:
b=9 f myvar &&
printf %s\\n \
"$fout_name" \
"$fout" \
"$myvar" \
"$a" "$b"
###OUTPUT###
Tue Jul 1 19:56:42 PDT 2014: 52 5
myvar
Tue Jul 1 19:56:42 PDT 2014: 52 5
Tue Jul 1 19:56:42 PDT 2014: 52 5
52
5
Вероятно, самое сложное, что нужно сделать правильно, когда дело доходит до двойной оценки, — это убедиться, что переменные не нарушают кавычки и не выполняют случайный код. Чем больше раз оценивается переменная, тем сложнее становится. Расширение параметров здесь очень помогает, и использование export
в противовес eval
гораздо безопаснее.
В приведенном выше примере f()
сначала назначается $fout
пустая ''
строка, а затем задаются позиционные параметры для проверки допустимых имен переменных. Если оба теста не пройдены, выдается сообщение stderr
и значение по умолчанию fout
назначается $fout_name
. Однако, независимо от тестов, $fout_name
всегда назначается либо fout
указанному вами имени, либо $fout
и, опционально, указанному вами имени всегда назначается выходное значение функции. Чтобы продемонстрировать это, я написал этот небольшой for
цикл:
for v in var '' "wr;\' ong"
do sleep 10 &&
a=${a:+$((a*2))} f "$v" || break
echo "${v:-'' #null}"
printf '#\t"$%s" = '"'%s'\n" \
a "$a" b "$b" \
fout_name "$fout_name" \
fout "$fout" \
'(eval '\''echo "$'\''"$fout_name"\")' \
"$(eval 'echo "$'"$fout_name"\")"
done
Он играет с некоторыми именами переменных и расширениями параметров. Если у вас есть вопрос, просто спросите. Этотолькозапускает те же несколько строк в функции, уже представленной здесь. Стоит упомянуть хотя бы, что переменные $a
и $b
ведут себя по-разному в зависимости от того, определены ли они при вызове или уже установлены. Тем не менее, не for
делает почти ничего, кроме форматирования набора данных и , предоставленного f()
. Взгляните:
###OUTPUT###
Wed Jul 2 02:50:17 PDT 2014: 51 96
var
# "$a" = '51'
# "$b" = '96'
# "$fout_name" = 'var'
# "$fout" = 'Wed Jul 2 02:50:17 PDT 2014: 51 96'
# "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul 2 02:50:17 PDT 2014: 51 96'
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul 2 02:50:27 PDT 2014: 103 92
'' #null
# "$a" = '103'
# "$b" = '92'
# "$fout_name" = 'fout'
# "$fout" = 'Wed Jul 2 02:50:27 PDT 2014: 103 92'
# "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul 2 02:50:27 PDT 2014: 103 92'
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul 2 02:50:37 PDT 2014: 207 88
wr;\' ong
# "$a" = '207'
# "$b" = '88'
# "$fout_name" = 'fout'
# "$fout" = 'Wed Jul 2 02:50:37 PDT 2014: 207 88'
# "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul 2 02:50:37 PDT 2014: 207 88'