题 如何在Bash中将变量设置为命令的输出?


我有一个非常简单的脚本,如下所示:

#!/bin/bash

VAR1="$1"    
MOREF='sudo run command against $VAR1 | grep name | cut -c7-'

echo $MOREF

当我从命令行运行此脚本并将参数传递给它时,我没有得到任何输出。但是,当我运行包含在其中的命令时 $MOREF 变量,我能得到输出。

我想知道如何获取需要在脚本中运行的命令的结果,将其保存到变量,然后在屏幕上输出该变量?


1087
2018-01-10 20:58


起源


一个相关的问题 stackoverflow.com/questions/25116521/... - Sandeepan Nath
顺便说一下,所有大写变量都是 由POSIX定义 对于对操作系统或shell本身有意义的变量名,而具有至少一个小写字符的名称保留供应用程序使用。因此,请考虑为自己的shell变量使用小写名称以避免意外冲突(请记住,设置shell变量将覆盖任何类似命名的环境变量)。 - Charles Duffy
顺便说一句,将输出捕获到变量中就可以了 echo 变量是一个 无用的 echo, 并且无用的变量。 - tripleee


答案:


除了反引号,你可以使用 $(),我觉得更容易阅读,并允许嵌套。

OUTPUT="$(ls -1)"
echo "${OUTPUT}"

引用(")保持多行值很重要。


1665
2018-01-10 21:04



我们可以为多线输出提供一些分隔符吗? - Aryan
仅供参考这称为“命令替换”: gnu.org/software/bash/manual/bashref.html#Command-Substitution - David Doria
白色空间(或缺少空白)很重要 - Ali
@ timhc22,花括号无关紧要;它只是重要的引号:扩展结果是否是字符串拆分和全局展开后才传递给 echo 命令。 - Charles Duffy
当变量后面跟着更多可以解释为变量名称一部分的字符时,可以使用大括号。 例如  ${OUTPUT}foo。在对变量执行内联字符串操作时也需要它们,例如 ${OUTPUT/foo/bar} - rich remer


更新 (2018):正确的方法是

$(sudo run command)

你使用的是错误的撇号。你需要 `不是 '。这个角色被称为“反叛”(或“重音”)。

喜欢这个:

#!/bin/bash

VAR1="$1"
VAR2="$2"

MOREF=`sudo run command against "$VAR1" | grep name | cut -c7-`

echo "$MOREF"

201
2018-01-10 21:00



反引号语法是过时的,你真的需要在变量插值中加上双引号 echo。 - tripleee
我想补充一点,你必须小心上面的赋值中'='周围的空格。您 没有任何空间 在那里,否则你将得到一个不正确的任务 - Zotov
tripleeee的评论是正确的。在cygwin(2016年5月),``不起作用 $() 作品。在看到此页面之前无法修复。 - toddwz
详细说明例如 更新(2018年) 不胜感激。 - Eduard


正如他们已经向你指出的那样,你应该使用'反引号'。

建议的备选方案 $(command) 也很有效,也更容易阅读, 但请注意,它仅适用于bash或korn shell(以及从这些shell派生的shell), 因此,如果您的脚本必须在各种Unix系统上真正可移植,那么您应该更喜欢旧的反引号表示法。


57
2018-01-11 22:14



他们非常谨慎。很久以前,POSIX已经弃用了反引号;在这个千年的大多数炮弹中,应该有更现代的语法。 (仍有遗留环境 咳嗽HP-UX咳嗽 它们在九十年代初期坚定不移。) - tripleee
不正确。 $() 与二十年前标准化的POSIX sh完全兼容。 - Charles Duffy
注意 /bin/sh 在Solaris 10上仍然无法识别 $(…)  - 和Solaris 11上的AFAIK也是如此。 - Jonathan Leffler
@JonathanLeffler实际上不再是Solaris 11的情况了 /bin/sh 是 ksh93。 - jlliagre
@tripleee-迟到三年回复:-)但我已经习惯了 $() 在过去10多年的HP-UX上的POSIX shell中。 - Bob Jarvis


我知道三种方法:

1)功能适用于此类任务:

func (){
ls -l
}

通过说来调用它 func

2)另一个合适的解决方案可能是eval:

var="ls -l"
eval $var

3)第三个是直接使用变量:

var=$(ls -l)
OR
var=`ls -l`

你可以很好地得到第三种解决方案的输出:

echo "$var"

还有令人讨厌的方式:

echo $var

45
2018-02-13 07:31



前两个似乎没有回答目前的问题,而第二个通常被认为是可疑的。 - tripleee
作为一个对bash来说全新的人,为什么呢 "$var" 好的 $var 讨厌? - Peter
@Peter stackoverflow.com/questions/10067266/... - tripleee


一些  我用来从命令设置变量的技巧

第二次编辑2018-02-12:添加一种特殊方式,请参见最底层!

2018-01-25编辑:添加样本功能 (用于填充有关磁盘使用情况的变量)

第一个简单的旧和兼容的方式

myPi=`echo '4*a(1)' | bc -l`
echo $myPi 
3.14159265358979323844

大多数兼容,第二种方式

由于嵌套可能变得很重,因此实现了括号

myPi=$(bc -l <<<'4*a(1)')

嵌套样本:

SysStarted=$(date -d "$(ps ho lstart 1)" +%s)
echo $SysStarted 
1480656334

读取多个变量(带有 bash化

df -k /
Filesystem     1K-blocks   Used Available Use% Mounted on
/dev/dm-0         999320 529020    401488  57% /

如果我只是想要 用过的 值:

array=($(df -k /))

你可以看到 排列 变量:

declare -p array
declare -a array='([0]="Filesystem" [1]="1K-blocks" [2]="Used" [3]="Available" [
4]="Use%" [5]="Mounted" [6]="on" [7]="/dev/dm-0" [8]="999320" [9]="529020" [10]=
"401488" [11]="57%" [12]="/")'

然后:

echo ${array[9]}
529020

但我更喜欢这个:

{ read foo ; read filesystem size used avail prct mountpoint ; } < <(df -k /)
echo $used
529020

1 read foo 会的 跳跃 标题行(变量 $foo 将包含类似的东西 Filesystem 1K-blocks Used Available Use% Mounted on

用于填充一些变量的示例函数:

#!/bin/bash

declare free=0 total=0 used=0

getDiskStat() {
    local foo
    {
        read foo
        read foo total used free foo
    } < <(
        df -k ${1:-/}
    )
}

getDiskStat $1
echo $total $used $free

诺塔: declare 为了便于阅读,不需要行。

关于 sudo cmd | grep ... | cut ...

shell=$(cat /etc/passwd | grep $USER | cut -d : -f 7)
echo $shell
/bin/bash

(请避免无用 cat!所以这只是1叉少:

shell=$(grep $USER </etc/passwd | cut -d : -f 7)

所有管道(|)暗示叉子。必须运行另一个进程,访问磁盘,库调用等等。

所以使用 sed 对于示例,将子进程限制为仅一个 叉子

shell=$(sed </etc/passwd "s/^$USER:.*://p;d")
echo $shell

bash化

但对于许多操作,主要是小文件,  可以自己完成这项工作:

while IFS=: read -a line ; do
    [ "$line" = "$USER" ] && shell=${line[6]}
  done </etc/passwd
echo $shell
/bin/bash

要么

while IFS=: read loginname encpass uid gid fullname home shell;do
    [ "$loginname" = "$USER" ] && break
  done </etc/passwd
echo $shell $loginname ...

更进一步 变量分裂...

看看我的回答 如何在Bash中的分隔符上拆分字符串?

替代方案:减少 叉子 通过使用后台长时间运行的任务

第二编辑2018-02-12: 为了防止像多个叉子一样

myPi=$(bc -l <<<'4*a(1)'
myRay=12
myCirc=$(bc -l <<<" 2 * $myPi * $myRay ")

要么

myStarted=$(date -d "$(ps ho lstart 1)" +%s)
mySessStart=$(date -d "$(ps ho lstart $$)" +%s)

因为 date 和 bc 可以工作 逐行

bc -l <<<$'3*4\n5*6'
12
30

date -f - +%s < <(ps ho lstart 1 $$)
1516030449
1517853288

我们可以用 长跑 后台进程重复工作,而不必启动新的 叉子 对于每个请求:

mkfifo /tmp/myFifoForBc
exec 5> >(bc -l >/tmp/myFifoForBc)
exec 6</tmp/myFifoForBc
rm /tmp/myFifoForBc

(当然,FD 5 和 6 必须被闲置!)...从那里,您可以通过以下方式使用此过程:

echo "3*4" >&5
read -u 6 foo
echo $foo
12

echo >&5 "pi=4*a(1)"
echo >&5 "2*pi*12"
read -u 6 foo
echo $foo
75.39822368615503772256

进入一个功能 newConnector

你可能找到了我的 newConnector 功能 GitHub.Com 或者 我自己的网站 (Nota在github上,有两个文件,在我的网站上,功能和演示被捆绑到一个文件中,可以使用或仅运行演示)

样品:

. shell_connector.sh

tty
/dev/pts/20

ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
  29019 pts/20   Ss     0:00 bash
  30745 pts/20   R+     0:00  \_ ps --tty pts/20 fw

newConnector /usr/bin/bc "-l" '3*4' 12

ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
  29019 pts/20   Ss     0:00 bash
  30944 pts/20   S      0:00  \_ /usr/bin/bc -l
  30952 pts/20   R+     0:00  \_ ps --tty pts/20 fw

declare -p PI
bash: declare: PI: not found

myBc '4*a(1)' PI
declare -p PI
declare -- PI="3.14159265358979323844"

功能 myBc 让您使用简单语法的后台任务,并使用日期:

newConnector /bin/date '-f - +%s' @0 0
myDate '2000-01-01'
  946681200
myDate "$(ps ho lstart 1)" boottime
myDate now now ; read utm idl </proc/uptime
myBc "$now-$boottime" uptime
printf "%s\n" ${utm%%.*} $uptime
  42134906
  42134906

ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
  29019 pts/20   Ss     0:00 bash
  30944 pts/20   S      0:00  \_ /usr/bin/bc -l
  32615 pts/20   S      0:00  \_ /bin/date -f - +%s
   3162 pts/20   R+     0:00  \_ ps --tty pts/20 fw

从那里,如果你想结束一个后台进程,你只需要关闭他的 FD

eval "exec $DATEOUT>&-"
eval "exec $DATEIN>&-"
ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
   4936 pts/20   Ss     0:00 bash
   5256 pts/20   S      0:00  \_ /usr/bin/bc -l
   6358 pts/20   R+     0:00  \_ ps --tty pts/20 fw

不需要,因为所有fd在主要过程结束时关闭。


36
2017-12-20 07:06



上面的嵌套示例是我正在寻找的。可能有一种更简单的方法,但我想要的是找出一个docker容器是否已存在,并在环境变量中给出其名称。所以对我来说: EXISTING_CONTAINER=$(docker ps -a | grep "$(echo $CONTAINER_NAME)") 是我正在寻找的声明。 - Capricorn1
@ capricorn1那是一个 无用的 echo;你想要的只是 grep "$CONTAINER_NAME" - tripleee
2018编辑:添加示例函数(用于填充有关磁盘使用情况的变量) - F. Hauri


只是为了与众不同:

MOREF=$(sudo run command against $VAR1 | grep name | cut -c7-)

22
2018-01-10 21:07





如果你想用multiline / multiple命令执行它,那么你可以这样做:

output=$( bash <<EOF
#multiline/multiple command/s
EOF
)

要么:

output=$(
#multiline/multiple command/s
)

例:

#!/bin/bash
output="$( bash <<EOF
echo first
echo second
echo third
EOF
)"
echo "$output"

输出:

first
second
third

运用 定界符 通过将长单行代码分解为多行代码,您可以非常轻松地简化操作。另一个例子:

output="$( ssh -p $port $user@$domain <<EOF 
#breakdown your long ssh command into multiline here.
EOF
)"

12
2018-06-01 17:38



第二个是什么 bash 在命令替换里面?您已经通过命令替换本身创建子shell。如果要放置多个命令,只需用换行符或分号分隔它们。 output=$(echo first; echo second; ...) - tripleee
@tripleee只是完成它的不同方式。 - Jahid
然后类似 'bash -c "bash -c \"bash -c ...\""' 也会“不同”;但我没有看到这一点。 - tripleee
我觉得我们没有正确沟通。我正在挑战有用性 variable=$(bash -c 'echo "foo"; echo "bar"')过度 variable=$(echo "foo"; echo "bar")  - 这里的文档只是一个引用机制,并没有真正添加任何东西,除了另一个无用的复杂功能。 - tripleee
当我使用heredoc和ssh时,我会精确地运行命令 ssh -p $port $user@$domain /bin/bash <<EOF 为了阻止 Pseudo-terminal will not be allocated because stdin is not a terminal. 警告 - F. Hauri


设置变量时确保你有 没有空间 之前和/或之后 = 标志。花了一个小时试图弄清楚这一点,尝试各种解决方案!这不酷。

正确:

WTFF=`echo "stuff"`
echo "Example: $WTFF"

将失败 有错误:(东西:未找到或类似)

WTFF= `echo "stuff"`
echo "Example: $WTFF"

10
2017-07-18 11:42



我之前没有注意到这一点。谢谢 :) - Ayyappa


你可以使用反蜱(也称为重音坟墓)或 $()。 像 - 作为 -

OUTPUT=$(x+2);
OUTPUT=`x+2`;

两者都有相同的效果。但OUTPUT = $(x + 2)更易读,也是最新的。


5
2018-02-09 08:03



bash: x+2: command not found - F. Hauri
实现括号以允许嵌套。 - F. Hauri


这是另一种方式,适用于某些文本编辑器,这些编辑器无法正确突出显示您创建的每个复杂代码。

read -r -d '' str < <(cat somefile.txt)
echo "${#str}"
echo "$str"

3
2018-05-10 21:44



这不涉及OP的问题,这是真的 命令替换不是 过程替代。 - codeforester
@codeforester但这也适合你吗? - Aquarius Power


有些人可能觉得这很有用。 变量替换中的整数值,即技巧使用的位置 $(()) 双括号:

N=3
M=3
COUNT=$N-1
ARR[0]=3
ARR[1]=2
ARR[2]=4
ARR[3]=1

while (( COUNT < ${#ARR[@]} ))
do
  ARR[$COUNT]=$((ARR[COUNT]*M))
  (( COUNT=$COUNT+$N ))
done

3
2017-11-22 11:59



这似乎与此问题无关。如果有人要问如何将数组中的数字乘以常数因子,这将是一个合理的答案,尽管我不记得曾经有人问过这个问题(然后是 for ((...)) 循环似乎是循环变量的更好匹配)。此外,您不应该为您的私有变量使用大写。 - tripleee
我不同意“相关性”部分。问题清楚地写着:如何在Bash中设置一个等于命令输出的变量?我添加了这个答案作为补充,因为我在这里寻找一个解决方案,帮助我后来发布的代码。关于大写的变种,谢谢你。 - Gus
你在这里捕获哪个命令的输出? - tripleee
这可以写 ARR=(3 2 4 1);for((N=3,M=3,COUNT=N-1;COUNT < ${#ARR[@]};ARR[COUNT]*=M,COUNT+=N)){ :;} 但我同意@tripleee:我不明白这是做什么的! - F. Hauri
@ F.Hauri ... bash越来越像perl越深入你了! - ropata