从管道或命令行参数读取文件名的 Bash 脚本?

Jef*_*eff 7 bash filenames stdin arguments

我希望我的脚本读取作为 glob 或 STDIN 给出的一堆文件名(可能有空格),并用它们做一些事情。我已经能够分别阅读任何一种方式,但不能将它们结合起来。

这从命令行读取 globs:

for filename in "$@"; do
    process_file "$filename"
done
Run Code Online (Sandbox Code Playgroud)

这从标准输入读取:

IFS=$'\n' read -d '' -r -a filenames
for filename in "${filenames[@]}"; do
    process_file "$filename"
done
Run Code Online (Sandbox Code Playgroud)

我真正想要的是从任何一个读取到一个数组中,这样我就不必将整个for filename in...循环复制两次。对不起,如果这很明显,我是 BASH 的新手。

编辑:我认为最好的办法是从 args 中读取它们,否则等待STDIN. 我该怎么做?

编辑:好的,问题不是我想的那样。问题是 process_file 也要求用户输入。有没有办法从STDINEOF 开始读取,存储它,然后再次开始要求输入?

Gil*_*il' 5

当您没有在命令行上指定任何文件名时,文本处理工具传统上从标准输入读取输入。您可以通过测试$#变量来检查是否有命令行参数。假设process_file如果不传递参数,则从标准输入读取:

if [ $# -eq 0 ]; then
  process_file
else
  for x; do
    process_file "$x"
  done
fi
Run Code Online (Sandbox Code Playgroud)

或者,如果process_file需要参数,请尝试传递它/dev/stdin以从标准输入读取:

if [ $# -eq 0 ]; then set /dev/stdin; fi
for x; do
  process_file "$x"
done
Run Code Online (Sandbox Code Playgroud)

当没有命令行参数时,您要求从标准输入读取文件名列表。当然,这是可能的,但我不建议这样做,因为这很不寻常。您的用户必须学习与大多数其他工具不同的约定。尽管如此,这是一种方法:

if [ $# -eq 0 ]; then
  set -f # turn off globbing
  IFS='
' # set newlines to be the only field separator
  set -- $(cat)
  set +f; unset IFS
fi
for x; do
  process_file "$x"
done
Run Code Online (Sandbox Code Playgroud)


F. *_*uri 4

新答案2021-10-18

bash:从参数 和/或 标准输入创建列表

考虑这个小脚本:

#!/bin/bash

declare -a ARGS=("$@")

while read -rt .02 arg;do
    ARGS+=($arg)
    done

declare -p ARGS
Run Code Online (Sandbox Code Playgroud)

你可以打电话给他们argsAndInput.sh索取样品,然后...

seq 1 10 | ./argsAndInput.sh foo bar baz
declare -a ARGS=([0]="foo" [1]="bar" [2]="baz" [3]="1" [4]="2" [5]="3" [6]="4" [
7]="5" [8]="6" [9]="7" [10]="8" [11]="9" [12]="10")

seq 1 10 | ./argsAndInput.sh 
declare -a ARGS=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8"
 [8]="9" [9]="10")

./argsAndInput.sh foo bar baz 
declare -a ARGS=([0]="foo" [1]="bar" [2]="baz")

./argsAndInput.sh 
declare -a ARGS=()
Run Code Online (Sandbox Code Playgroud)

根据您的需要,您可以减少超时 ( read -t .02) 或使用:

while read -t 0 &&
    read -r arg;do
        ARGS+=($arg)
done
Run Code Online (Sandbox Code Playgroud)

如果您确定您的输入在bash 运行之前已准备好。

旧答案

您可以使用xargsto转换 STDIN(即使有很多条目,比命令行缓冲区大

或者,也许是这样的:

if [ $# -gt 0 ] ;then
    for filename in  "$@" ; do
        process_file "$filename"
    done
  else
    IFS=$'\n' read -d '' -r -a filenames
    for filename in "${filenames[@]}"; do
        process_file "$filename"
    done
  fi
Run Code Online (Sandbox Code Playgroud)

或两者:

  IFS=$'\n' read -d '' -r -a filenames
  for filename in "${filenames[@]}" "$@" ; do
        process_file "$filename"
    done
  fi
Run Code Online (Sandbox Code Playgroud)

或者通过添加这样的选项,例如仅-l代表行参数,以确保不会读取(等待)STDIN

case "$1" in
    -l )
        shift
        for filename in "$@" ; do
            process_file "$filename"
        done
        ;;
     * )
        for filename in "${filenames[@]}" "$@" ; do
            process_file "$filename"
        done
        ;;
    esac
Run Code Online (Sandbox Code Playgroud)

(未经测试!)