如何将变量的值传递给命令的标准输入?

160

我正在编写一个应该要比较安全的shell脚本,即不通过命令参数传递安全数据,并且最好不使用临时文件。如何将变量传递到命令的标准输入中?

如果不可能,那么如何正确地使用临时文件完成这个任务?


攻击者能否更改 $PATH?以便将 cat 替换为 /bin/cat "$@" | tee /attacker/can/read/this/file - 12431234123412341234123
如果您从重复的地方来到这里,您可能尝试了variable=$("$something" | command),而您想要的是variable=$(echo "$something" | command) - tripleee
9个回答

277

在Bash中将一个值传递给标准输入非常简单:

your-command <<< "$your_variable"

始终确保您在变量表达式周围加上引号!

请注意,这可能只适用于 bash,在 sh 中可能无法正常工作。


48
这里字符串的 <<< 操作符并不保证可用性,因为它们不在 POSIX 标准中,就我所知。只要你的运行环境是 bash,这个操作符可以按期望工作。我提到这点是因为 OP 提到了“shell”,而非特指 bash。虽然 OP 标记了问题的标签为 bash,所以这仍然是一个可接受的答案。 - J. M. Becker
6
printf '%s\n' "$var"echo "$var" 输出的结果相同,但是前者在 var=-en 的情况下不会出错,而且甚至不需要 bash。请注意,此处仅为翻译,无其他返回信息。 - Camilo Martin
5
注意,对于这里字符串,会在字符串末尾添加一个换行符。 - pdr
1
我发现这种语法的一个缺点是,将第一个命令的输出导入到其他命令中不像使用 echo "$your_variable" | command1 | command2 那样直观。在这种情况下,使用here strings的语法会是什么呢? - patricknelson
3
@chunk_split 如果你喜欢的话,你可以把这个 here string 放在命令的开头:<<< "$your_variable" command1 | command2 - Martin
显示剩余6条评论

102

简单但易错:使用echo

像这样简单的代码可以顺利完成任务:

echo "$blah" | my_cmd

需要注意的是,如果$blah包含-n-e-E等内容,或者包含反斜杠(bash的echo副本在默认情况下保留文字反斜杠,但即使未使用-e选项,如果启用了可选的XSI扩展,它也会将其视为转义序列并替换为相应的字符),则此方法可能无法正常工作。

更复杂的方法:使用printf

printf '%s\n' "$blah" | my_cmd

这种方法没有上述缺点:所有可能的 C 字符串(不包含 NULs 的字符串)都会被原样打印。


也许你可以在这个答案中加入 printenv blah | my_cmd - mallwright
3
"printenv" 只能打印已经导出的变量。 - Iain Samuel McLean Elder

25
(cat <<END
$passwd
END
) | command

cat并不是必须的,但它有助于更好地组织代码,并允许您将更多命令用括号作为输入到您的命令中。


1
但是这种方法允许您将多行传递给您的命令,并且还允许在 $passwd 中包含空格。 - PoltoS
6
这是目前为止最好的答案,它不会泄漏变量内容以供管道或进程窥探。 - user2688272

22

请注意,'echo "$var" | command' 操作意味着标准输入仅限于被回显的行。如果您还希望连接终端,则需要更高级的操作:

{ echo "$var"; cat - ; } | command

( echo "$var"; cat -   ) | command
这意味着第一行或几行将是$var的内容,而其余部分将来自于cat读取的标准输入。如果命令没有执行任何太复杂的操作(例如尝试打开命令行编辑器或像vim一样运行),那么这应该没问题。否则,您需要变得非常高级 - 我认为expect或其派生版本可能比较适合。
命令行符号实际上是完全相同的,但是对于括号,需要第二个分号,而对于圆括号则不需要。

(echo“$ LIST”; cat -)| sed 1q 这对我有用,但当我运行此脚本时需要按ctrl d? - Gert Cuykens
是的;cat - 会继续从键盘读取,直到遇到EOF或中断,因此您需要通过键入control-D来告诉它EOF。 - Jonathan Leffler
如果您不需要终端输入,可以完全不使用cat,就像第一行一样。或者您可以使用cat列出文件。如果您想让命令读取终端输入,则必须告诉它何时已经到达输入的末尾。或者您可以使用( echo "$var"; sed /quit/q - ) | command;这将一直持续到您键入包含“quit”的行为止。您可以无限创意地处理它。请注意,有一个旧的城市传说,说有一个程序在用户开始使用厄瓜多尔时停止工作。他们会输入首都基多的名称,然后程序退出。 - Jonathan Leffler
echo "$LIST" | head -n1 可以正常工作,但 echo "$LIST" | sed 1q 却不能? - Gert Cuykens
不知道,它们应该是等价的。 - Jonathan Leffler
显示剩余2条评论

18
这种强大且便携的方法已经出现在评论中了。它应该是一个独立的回答。

这种强大且便携的方法已经出现在评论中了。它应该是一个独立的回答。

printf '%s' "$var" | my_cmd
或者
printf '%s\n' "$var" | my_cmd

注:

  • printfecho更好,原因在这里:为什么printfecho更好?
  • printf "$var"是错误的。第一个参数是格式字符串,在其中各种序列如%s\n会被解释。要正确传递变量,它不能被解释为格式。
  • 通常变量不包含尾随换行符。前面的命令(使用%s)将变量按原样传递。然而,处理文本的工具可能会忽略或抱怨不完整的行(参见为什么文本文件要以换行符结尾?)。因此,您可能需要后面的命令(使用%s\n),它将一个换行符附加到变量的内容中。非显然的事实:

    • Bash中的here字符串(<<<"$var" my_cmd)确实附加了一个换行符。
    • 任何附加换行符的方法都会导致my_cmd的标准输入非空,即使变量为空或未定义。

16

我喜欢Martin的答案,但它根据变量中的内容存在一些问题。这个

your-command <<< """$your_variable"""

如果您的变量包含"!,那么效果会更好。


8
为什么?另外,我无法复制任何问题:foo1=-; foo2='"'; foo3=\!; cat<<<"$foo1"; cat<<<"$foo2"; cat<<<"$foo3"在我的环境下运行良好。这三个 " 到底是做什么用的?据我所知,你只是在前后添加了一个空字符串。 - phk
尝试一些真实的东西。比如你的命令是ssh somehost,而你的变量是一个shell脚本。 - Robert Jacobs
这似乎会添加一个换行符。 - masterxilo
7
在Bash解析器中,"""foo""""foo"的处理方式是完全相同的。""只是一个空的引号对——在没有任何内容的情况下开始和结束一个带引号的字符串;因此,你可以将"$your_variable"与前面和后面的空引号串联起来。 - Charles Duffy
1
即使在双引号中,'!'(非)在启用历史扩展的交互式 shell 中会导致许多问题,但这也是关闭历史扩展的一个很好的理由,因此交互式 shell 和运行脚本的 shell 解析代码的方式相同。 - Charles Duffy
1
为什么 ! 的行为不遵循我上面描述的规则?因为历史扩展发生在常规解析之前;这是一个非常混乱的功能,如果一开始就禁用它,每个人都会更好。 - Charles Duffy

11
根据Martin的回答,Bash有一个名为Here Strings的功能(它本身是更广泛支持的Here Documents功能的变体):

3.6.7 Here Strings

A variant of here documents, the format is:

<<< word

The word is expanded and supplied to the command on its standard input.

请注意,Here Strings 似乎只适用于 Bash,因此为了提高可移植性,建议使用原始的 Here Documents 功能,如 PoltoS's answer 所述:
( cat <<EOF
$variable
EOF
) | cmd

或者,以上的一个更简单的变体:
(cmd <<EOF
$variable
EOF
)

你可以省略 (),除非你希望将其进一步重定向到其他命令。

3

试试这个:

echo "$variable" | command

但是,如果 echo 正在运行,那么 $variable 的内容不会出现在 ps -u 的输出中吗? - user283145
2
不会。echo是一个内置命令,因此在ps中没有进程可以显示。 - unbeli
2
注意变量中的空格:echo "$variable" 更好。 - Jonathan Leffler
没错,Bash 会吃掉双空格,更智能的 Shell 不会。 - unbeli

0

如果您从重复的页面来到这里,您可能是一个初学者,尝试做类似以下的事情:

"$variable" >file

或者

"$variable" | wc -l

显然你想表达的是类似于

echo "$variable" >file
echo "$variable" | wc -l

(真正的初学者也会忘记引号;通常使用引号,除非你有特定的理由省略它们,至少在你理解引用之前。)


顺便提一下,在字符串中计算有多少行通常也是反模式;请参阅“无用的 wc 用法”。 - tripleee
如果$variable包含一个有效的命令,比如文本echogit-commit,那么"$variable"不是语法错误(尽管通常不要将命令存储在变量中,请参见https://mywiki.wooledge.org/BashFAQ/050)。 - tripleee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接