web-dev-qa-db-ja.com

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

スクリプトに、globまたはSTDINから指定された一連のファイル名(スペースが含まれている場合があります)を読み取り、それらを使用して処理を実行したいと思います。私はどちらの方法でも別々に読むことができましたが、それらを組み合わせることはできませんでした。

これは、コマンドラインからグロブを読み取ります。

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...ループ全体を2回複製する必要がなくなります。これが明らかな場合は申し訳ありませんが、私はBASHを初めて使用します。

編集:与えられた場合は引数から読み取り、それ以外の場合はSTDINを待つのが最善だと思います。どうすればいいですか?

編集:OK、問題は私が思っていたものではありません。問題は、process_fileもユーザー入力を要求することです。 STDINからEOFまで読み取り、それを保存して、再度入力を求める方法はありますか?

7
Jeff

xargsからtransformSTDIN(エントリが多く、コマンドラインバッファよりもはるかに大きい)からargumentsを使用できます。

または、次のようなものかもしれません。

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
F. Hauri

コマンドラインでファイル名を指定しない場合、テキスト処理ツールは従来、標準入力から入力を読み取ります。 $#変数をテストすることにより、コマンドライン引数があるかどうかを確認できます。引数を渡さない場合、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

コマンドライン引数がない場合に、標準入力からファイル名のリストを読み取るように要求しました。もちろんそれは可能ですが、それは珍しいのでお勧めしません。ユーザーは、他のほとんどのツールとは異なる規則を学ぶ必要があります。それにもかかわらず、これを行う1つの方法があります。

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

これは私のために働きます:

for filename in ${*:-`cat`}; do
    process_file "$filename"
done

${*:-`cat`}

最初にコマンドライン引数を展開します。これが定義されていない場合(bashパラメーター置換:-)、パイプからstdinを使用します。

1
klimaheld

bash4.4以上:

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

for arg do
   something with "$arg"
done
1

これは、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
1
SkyLeach