shell命令的标准输入(stdin)

在 shell 命令中,有多种方式可以灵活地控制命令的标准输入(stdin),熟练掌握这些技巧有时会起到事半功倍的效果。

管道符号(|)

管道操作可以将一个命令的输出重定向到另一个命令的输入:

1
echo "$string" | command

这是最方便,也最为广泛使用的一种方式。但这种方式有个缺陷,即管道符之后的命令是在一个子 shell 进程中运行的,它的运行效果无法作用到当前 shell 进程。

考虑以下命令:

1
2
echo "hello world" | read first second
echo $second $first # will output nothing

会发现第二条 echo 命令的输出为空,并非预期中的 world hello(注意,一些特殊的 shell 如 zsh 不存在该问题)。事实上,read 命令确实正确地读取了两个变量,但之后该 shell 子进程结束,控制权回到主进程,变量又被丢弃了。

为了避免这种情况,可以使用花括号把 shell 子进程中的所有命令括起来:

1
2
3
4
echo "hello world" | {
read first second
echo $second $first # will output "world hello"
}

这使得第二个 echo 命令能够正常输出 world hello,但仍然无法把 firstsecond 两个变量带到主进程中来。

here-string(<<<)

继续上面的例子,除了管道符之外,我们有没有办法把字符串"hello world"以标准输入的形式传递给 read 命令呢?

有。一个非常方便的办法是使用“here-string”:

1
2
read first second <<< "hello world"
echo $second $first # will output "world hello"

上述命令不仅能够符合预期地输出 world hello,而且 firstsecond 两个变量也被保存到了当前 shell 中,随时可用。

here-document(<<)

here-document 可以认为是 here-string 的高阶形式,它支持多行输入,适合输入大段文本。

在 shell 命令行中使用方式如下:

1
2
3
4
5
6
$ cat <<EOF
> hi
> there
> EOF
hi
there

注意:其中的 $ 表示命令提示符,> 表示换行,均由 shell 提供,并非用户输入。EOF 为标志字符串,代表输入的开头和结尾,可以替换为其他任意字符串。

here-documnet 在 shell 脚本中使用的例子如下:

1
2
3
4
5
6
cat > out.txt <<FILE
foo
bar
bar bar
foo foo
FILE

这段脚本会将两个 FILE 之间的那段文本写入 out.txt 文件中。

输入重定向(<)

输入重定向符号(<)后需要接一个文件路径,表示将文件的内容重定向到标准输入。例如:

1
2
3
4
5
echo "hello world" > tmp.txt
cat tmp.txt # will output "hello world"

read first second < tmp.txt
echo $second $first # will output "world hello"

参考