处理 bash 脚本中的管道标准输入和位置参数的通用函数

处理 bash 脚本中的管道标准输入和位置参数的通用函数

我的脚本的工作原理如下:

./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是两个非选项参数,尽管以 开头,但仍被视为这样,-因为它们位于--选项结束之后争论。

您可以使用内置的标准getoptsshell 来处理符合该标准约定的选项。

在 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,但每次调用也会获取file1file2

答案2

为了避免在没有管道时等待输入,请在阅读之前对其进行测试:

[ -p /dev/stdin ] && read PIPEIN

在 Ubuntu 的 Bash 5 上测试。

相关内容