关于shell:如何在bash中将变量设置为命令的输出?

How do I set a variable to the output of a command in Bash?

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

1
2
3
4
5
6
#!/bin/bash

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

echo $MOREF

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

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


除了倒勾(`command`)之外,您还可以使用$(command),我觉得它更容易阅读,并且允许嵌套。

1
2
3
4
5
6
OUTPUT="$(ls -1)"
echo"${OUTPUT}"

MULTILINE=$(ls \\
   -1)
echo"${MULTILINE}"

引用(")对于保存多行值很重要。


正确的方法是

1
$(sudo run command)

如果要使用撇号,则需要`,而不是'。这个字符被称为"背景音"(或"重音符")。

这样地:

1
2
3
4
5
6
7
8
#!/bin/bash

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

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

echo"$MOREF"


正如他们已经向你指出的,你应该使用"倒计时"。

提出的替代方案$(command)同样有效,也更容易阅读,但请注意,它仅适用于bash或kornshell(以及由此派生的shell)。因此,如果您的脚本必须在各种UNIX系统上都是可移植的,那么您应该更喜欢使用旧的backticks表示法。


我用一些bash技巧从命令中设置变量

第二次编辑2018-02-12:添加不同的方式,在底部搜索长期运行的任务!

2018-01-25编辑:添加示例函数(用于填充关于磁盘使用的变量)

第一种简单、古老、兼容的方式

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

大部分兼容,第二种方式

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

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

嵌套样本:

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

读取多个变量(使用bashims)

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

如果我只想要使用的值:

1
array=($(df -k /))

您可以看到数组变量:

1
2
3
4
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]="/")'

然后:

1
2
echo ${array[9]}
529020

但我更喜欢这样:

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

1st read foo将跳过标题行(变量$foo将包含类似Filesystem 1K-blocks Used Available Use% Mounted on的内容)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/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行,只是为了可读性。

关于EDOCX1[4]

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

(请避免无用的cat!所以这就少了1个叉子:

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

所有管道(|表示叉。在必须运行另一个进程的地方,访问磁盘、库调用等等。

因此,使用sed作为样本,将子流程限制为仅一个分叉:

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

对于bashims:

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

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

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

进一步了解变量拆分…

请看一下我对如何在bash中分隔符上拆分字符串的答案。

备选方案:通过使用后台长时间运行任务减少分叉

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

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

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

这很好用,但多用叉子又重又慢。

datebc这样的命令可以进行很多操作,一行一行!!

见:

1
2
3
4
5
6
7
8
bc -l <<<$'3*4\
5*6'

12
30

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

因此,我们可以使用长时间运行的后台进程来完成许多作业,而不必为每个请求启动一个新的分叉。

我们只需要一些文件描述符和FIFOS就可以正确地做到这一点:

1
2
3
4
mkfifo /tmp/myFifoForBc
exec 5> >(bc -l >/tmp/myFifoForBc)
exec 6</tmp/myFifoForBc
rm /tmp/myFifoForBc

(当然,fd 56必须不用!)…从那里,您可以通过以下方式使用此过程:

1
2
3
4
5
6
7
8
9
10
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

您可以在github.com或我自己的网站上找到我的newConnector功能(不在github上,有两个文件,在我的网站上,功能和演示被捆绑成一个文件,可以从中获得供使用或仅用于演示)

Sample:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
. 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允许您使用简单语法的后台任务,对于日期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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\
"
${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:

1
2
3
4
5
6
7
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都将关闭。


我知道三种方法:

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

1
2
3
func (){
ls -l
}

调用它的方法是说func

2)另一个合适的解决方案是Eval:

1
2
var="ls -l"
eval $var

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

1
2
3
var=$(ls -l)
OR
var=`ls -l`

您可以很好地获得第三个解决方案的输出:

1
echo"$var"

也有一些不好的地方:

1
echo $var


只是为了与众不同:

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


设置变量时,确保在=符号之前和/或之后没有空格。字面上花了一个小时试图解决这个问题,尝试各种解决方案!这不酷。

对的:

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

将因错误而失败:(资料:未找到或类似)

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


如果要使用多行/多命令/s执行此操作,则可以执行以下操作:

1
2
3
4
output=$( bash <<EOF
#multiline/multiple command/s
EOF

)

或:

1
2
3
output=$(
#multiline/multiple command/s
)

例子:

1
2
3
4
5
6
7
8
#!/bin/bash
output="$( bash <<EOF
echo first
echo second
echo third
EOF
)"

echo"$output"

输出:

1
2
3
first
second
third

使用HereDoc,您可以很容易地将长的单行代码分解为多行代码,从而简化工作。另一个例子:

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


你也需要使用

$(command-here)

1
`command-here`

例子

1
2
3
4
5
6
7
8
#!/bin/bash

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

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

echo"$MOREF"


你可以使用反勾号(也被称为重音标记)或$()。像AS一样

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

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


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

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


还有两种方法:

请记住,空间在bash中非常重要。因此,如果希望命令运行,请按原样使用,而不引入任何空间。

  • 下面将harshil分配给l,然后打印它

    1
    2
    L=$"harshil"
    echo"$L"
  • 下面将命令tr的输出分配给l2。tr正在对另一个变量l1进行操作。

    1
    L2=$(echo"$L1" | tr [:upper:] [:lower:])

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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