パイプまたはコマンドライン引数からファイル名を読み取るBashスクリプト?

パイプまたはコマンドライン引数からファイル名を読み取るBashスクリプト?

私のスクリプトは、globまたはSTDIN(スペースを含む)として提供された複数のファイル名を読み、それを使用して操作を実行したいと思います。 2つの方法を別々に読むことはできますが、組み合わせることはできません。

これはコマンドラインから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...私が本当に望むのは、これらのうちの1つを配列に読み込んでループ全体を2回繰り返す必要がないようにすることです。これが明らかな場合は申し訳ありません。私は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

入力が準備されたと確信している場合今後バッシュを実行する必要があります。

古い回答

あなたは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

関連情報