![处理 bash 脚本中的管道标准输入和位置参数的通用函数](https://linux22.com/image/203309/%E5%A4%84%E7%90%86%20bash%20%E8%84%9A%E6%9C%AC%E4%B8%AD%E7%9A%84%E7%AE%A1%E9%81%93%E6%A0%87%E5%87%86%E8%BE%93%E5%85%A5%E5%92%8C%E4%BD%8D%E7%BD%AE%E5%8F%82%E6%95%B0%E7%9A%84%E9%80%9A%E7%94%A8%E5%87%BD%E6%95%B0.png)
我的脚本的工作原理如下:
./script file1 file2 file3 ... -x -st -i
或者
cat filelist.txt | ./script -x -st -i
或者
./script <<< "$(cat filelist) -x -st -i
有人可以帮助使用从文件名设置数组的常用函数,以防文件由位置参数或管道或重定向给出
如何在单个 shell 脚本中涵盖上述所有情况?
尝试过:
input="$(</dev/stdin)"
Arr_tmp+=("$@")
if ! echo "${Arr_tmp[@]}" | grep 'file_regex'; then Array+=( $(echo "$input") ); else Array+=("$(echo "$@" | grep 'file_regex')"); fi
或者
input="$(</dev/stdin)"
IFS=$'\n' read -ra Array -d '' <<< "$(echo $input)"
if [[ "${#Array[@]}" == 0 ]]; then
Arr_tmp+=("$@")
fi
但没有任何作用
上述问题是当没有标准输入通过管道传输时,脚本在读取时被击中。如果没有通过管道传输标准输入,有没有办法防止脚本读取并回退到参数?
答案1
首先,在非选项参数之后期待选项是不好的做法。
标准命令行界面是在非选项参数之前有选项,并允许可选--
标记选项的结尾,以便您可以接受恰好以 开头的非选项参数-
:
myscript -c -ofile.out -- -file1-.in -file2-.in
与...一样:
myscript -co file.out -- -file1-.in -file2-.in
例如,-o
一个带有参数的选项,-c
一个不带有参数的选项,--
标志着选项的结束,而-file1-.in
,-file2-.in
是两个非选项参数,尽管以 开头,但仍被视为这样,-
因为它们位于--
选项结束之后争论。
您可以使用内置的标准getopts
shell 来处理符合该标准约定的选项。
在 Linux 上,您还可以使用getopt
来自 的实用程序util-linux
来处理标准选项,也可以使用 GNU 风格的长选项或带有可选参数的选项,并允许在非选项参数之后使用选项(除非$POSIXLY_CORRECT
在环境中),但仍然--
遵循选项结束标记。getopt
将负责在非选项之前使用选项正确地重新排序参数。
要处理通过命令行或标准输入给出的输入,您需要执行以下操作:
while getopts...; do
# process options
...
done
# option processing done.
shift "$((OPTIND - 1))"
# now the positional parameters contain non-option arguments
if (( $# )); then
args=("$@")
else
# no non-option arguments, read them one per line from stdin
readarray -t args
fi
这((...))
是一种 kshism(也得到 的支持bash
),readarray
一种 bashism。在 中sh
,你会这样做:
if [ "$#" -eq 0 ]; then
# no non-option arguments, read them from stdin
IFS='
' # split on newline
set -o noglob
set -- $(cat) # read and split+glob with glob disabled
fi
结果存储在位置参数 ( $1
, $2
... "$@"
) 中,而不是$args
上面 bash 示例中的数组中。请注意,在这种情况下,空行将被丢弃。
另请注意,文件路径可以包含除 0 之外的任何字节值,并且包括换行符,因此您不能每行传递一个任意文件路径列表。
上面,如果命令行上没有传递非选项参数,我们只准备从标准输入。这就是大多数文本实用程序所做的。例如,grep -e regexp -- file1 file2
在这些文件中搜索 regexp,但grep -e regexp
在 stdin 中搜索。
如果您想允许通过参数或标准输入提供输入,您必须总是从标准输入读取:
readarray -t args
args+=("$@")
将两者合并为$args
.
当用户不想通过标准输入传递任何内容时,他们总是可以这样做:
myscript arg1 arg2 < /dev/null
当 stdin 是终端时,要以交互方式传递列表,您需要键入文件列表并以Ctrl+结尾d。
您可能会想做:
if [ -t 0 ]; then
args=()
else
readarray -t args
fi
args+=("$@")
仅在不是 tty 设备时读取 stdin,但我认为这是一个坏主意;用户很容易忽视脚本的事实可能使用 stdin,当他们在 stdin 重定向到除 之外的文件(/dev/null
例如while IFS= read -r...; do myscript ...; done < some-file
.
正如 @ilkkachu 所建议的,您还可以添加一个选项来从标准输入或任意文件获取输入,以便用户可以决定何时以及何时不读取标准输入。
另一个选项是使用xargs
将输入流转换为参数列表的工具。例如,使用 GNU xargs
:
xargs -d '\n' -a filelist.txt myscript -x -st -i file1 file2
将使用myscript
,file1
以及file2
每行的内容filelist.txt
作为参数进行调用。但请注意,如果列表是 bing,则它可能会分解为多次调用,myscript
每次调用都会获取 中的行的子集filelist.txt
,但每次调用也会获取file1
和file2
。
答案2
为了避免在没有管道时等待输入,请在阅读之前对其进行测试:
[ -p /dev/stdin ] && read PIPEIN
在 Ubuntu 的 Bash 5 上测试。