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

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

我希望我的脚本读取一堆以 glob 或 STDIN 形式给出的文件名(可能有空格),并用它们做一些事情。我可以单独阅读这两种方式,但不能将它们结合起来。

这从命令行读取 glob:

for filename in "$@"; do
    process_file "$filename"
done

这是从 STDIN 读取的:

IFS=$'\n' read -d '' -r -a filenames
for filename in "${filenames[@]}"; do
    process_file "$filename"
done

我真正想要的是将其中任何一个读入数组,这样我就不必将整个for filename in...循环重复两次。抱歉,如果这很明显,我是 BASH 新手。

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

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

答案1

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

if [ $# -eq 0 ]; then
  process_file
else
  for x; do
    process_file "$x"
  done
fi

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

if [ $# -eq 0 ]; then set /dev/stdin; fi
for x; do
  process_file "$x"
done

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

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

答案2

新答案2021-10-18

bash:从中创建列表争论 和/或 标准输入

考虑这个小脚本:

#!/bin/bash

declare -a ARGS=("$@")

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

declare -p ARGS

你可以打电话给他们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=()

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

while read -t 0 &&
    read -r arg;do
        ARGS+=($arg)
done

如果您确定您的输入已准备好你的 bash 应该运行。

旧答案

您可以xargs使用转换 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

或两者:

  IFS=$'\n' read -d '' -r -a filenames
  for filename in "${filenames[@]}" "$@" ; do
        process_file "$filename"
    done
  fi

或者通过添加这样的选项,就像-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

(未经测试!)

答案3

这是我编写的脚本,用于安全地在 cygwin X11 gvim 和 windows gvim 中打开文件。它被设计为外壳包装器,这是执行您想要的操作的部分:

if [ ${#} -ne 0 ]; then #if we have filenames as CLArgs...
    [[ ! -z ${DEBUG} ]] && echo "Got arguments [${#}]:'${@}'"
    for OFILE in "${@}"; do
        [[ -h ${OFILE} ]] && OFILE="$(readlink -f "${OFILE}")"
        [[ ${WINVIM} == true ]] && OFILE=$(cygpath -wal "${OFILE}")
        echo "\"${VIMRUN}\" --servername GVIM $RT \"${OFILE}\""
        "${VIMRUN}" --servername GVIM $RT "${OFILE}" &
        RT="--remote-tab"
    done
else #otherwise read from stdin safely and handle spaces...
    while read OFILE; do
        [[ -h ${OFILE} ]] && OFILE="$(readlink -f "${OFILE}")"
        [[ ${WINVIM} == true ]] && OFILE=$(cygpath -wal "${OFILE}")
        echo "\"${VIMRUN}\" --servername GVIM $RT \"${OFILE}\""
        "${VIMRUN}" --servername GVIM $RT "${OFILE}" &
        RT="--remote-tab"
    done
fi

答案4

bash4.4及以上:

if [ "$#" -eq 0 ]; then
  readarray -d '' args
  set -- "${args[@]}"
fi

for arg do
   something with "$arg"
done

相关内容