管道输出到bash功能
1. 简介
管道 是Bash 的一个极其强大和通用的特性。它们最常与 Bash 或操作系统提供的本机命令一起使用。管道 可能并不总是最有效的方法,当某些 shell 命令本机支持输出处理时甚至可能是多余的,如find 命令:
find -exec some_script {} \;
但是,当命令不支持标准输出的本机处理并且我们需要将数据流式传输到 shell 脚本时,将输出通过管道传输到 Bash 函数是正确的选择。
在本教程中,我们将首先通过管道输出到脚本,然后到该脚本中的函数,最后,如何安全地执行此操作来探索此功能。
2. 如何通过管道传输到脚本
让我们重新审视一些基本面。Bash 中的管道获取一个进程的标准输出并将其作为标准输入传递给另一个进程。Bash 脚本支持可以在命令行中传入的位置参数。
然后可以使用脚本中的 bash 定义的变量*$0到$9*来检索这些参数:
$ more myscript.sh
#!/bin/bash
echo $1
现在,当我们使用参数调用脚本时,我们可以看到变量*$1*中捕获的第一个参数并输出到 shell:
$ ./myscript.sh hello
hello
指导原则 #1:在 Bash 中执行的命令从启动它们的进程接收标准输入。
我们可以通过定义一个基本脚本sample_one.sh来看到这一点,它将一些未定义 的数据分类到一个文件中。此时它是未定义的数据,因为cat需要接受一个文件或挂载点的参数,但没有定义:
$ more sample_one.sh
#!/bin/bash
cat > file_one.txt
如果我们执行一些命令,在本例中为date ,并将输出通过管道传输到我们的脚本,然后我们可以在file_one.txt中查看结果:
$ date | ./sample_one.sh
$ more file_one.txt
Sat Oct 23 22:03:33 SAST 2021
这可能看起来有点神奇,但它是两件事的结合。
- sample_one.sh从启动它的进程接收其标准输入(date |)。
- cat按照它的设计,在独立使用或不带参数使用时,会复制标准输入并将其重定向到标准输出。
利用我们学到的知识,我们现在可以将其应用于我们自己的自定义脚本中的函数。
3. 如何通过管道传递给函数
指导原则 #2:传递给脚本的标准输入可由调用的第一个函数访问。
我们可以通过重用之前的代码来验证这一点,除了这次我们将在function 中这样做。不要忘记调用函数(read_stdin),否则脚本不会执行:
$ more sample_two.sh
#!/bin/bash
function read_stdin()
{
cat > file_two.txt
}
read_stdin
重要的是要注意该函数不需要声明的参数或变量来接受或保持标准输入。这会自动从命令行传递并从父进程继承到我们的函数中。我们可以再次使用date命令并查看file_two.txt的内容,这将与以前相同——当然,时间已更新并符合操作系统定义的区域设置 :
$ date | ./sample_two.sh
$ more file_two.txt
Sat Oct 23 23:03:33 SAST 2021
很棒的工作——我们已经完成了一个 bash 函数的输出管道。但这不是很可读,对于经验较少的人来说很难维护,并且有可能出现错误。让我们改进它。
4. 如何安全管道
4.1. /dev/stdin的可读性
为了追求可读性并按照 Unix 设计(一切都是文件),我们可以使用文件/dev/stdin 引用标准输入:
$ more sample_three.sh
#!/bin/bash
function read_stdin()
{
cat < /dev/stdin > file_three.txt
}
read_stdin
file_three.txt中的输出与我们之前看到的没有什么不同。这种方法的优点是标准输入没有隐藏在cat的隐藏知识中,而是明显地分配给了一个变量。这可以为新开发人员或管理员节省一些调试和故障排除的时间。
4.2. 可维护性与read
另一种从管道接收输入到函数的机制是*read *实用程序。这会将标准输入中的单个逻辑行读取到一个或多个 shell 变量中:
$ more sample_four.sh
#!/bin/bash
function getInput()
{
read in echo $in > file_four.txt
}
getInput
我们将标准输入读入用户定义的 shell 变量*$in*。我们不使用cat,而是使用echo 将该变量的内容写入标准输出和file_four.txt。
4.3. test的鲁棒性
到目前为止,示例脚本存在一个常见问题——它们很脆弱。如果用户在没有实际传递标准输入的情况下执行了它们中的任何一个,那么脚本就会挂起。事实上,它会无限期地等待输入。我们可以按照下面的示例进行模拟:
$ ./sample_one.sh
为了解决这个问题,我们可以首先使用*test 验证输入以确认是否设置了变量。事实上,我们也可以使用test*作为位置参数。
例如,我们可以编写一个只检查文件类型和比较值的测试:
$ more sample_five.sh
#!/bin/bash
function getInput() {
if test -n "$1"; then
echo "Read from positional argument $1";
elif test ! -t 0; then
echo "Read from stdin if file descriptor /dev/stdin is open"
cat > file4.txt
else
echo "No standard input."
fi
}
getInput
让我们仔细看看这个脚本:
- test的第一个条件使用,带有*-n*参数,检查位置参数的长度是否大于零。
- test的第二个条件使用,带有*-t*参数,检查标准输入文件描述符是否在终端上打开。
- else块确认没有标准输入。
这绝对是验证标准输入的更强大的解决方案,并且使用简单。虽然它涵盖了大多数用例,但它在绑定到终端方面确实存在缺点。对于更高级的输入验证,我们可以考虑moreutils ,特别是ifne 二进制文件。