私はこの問題をいくつかの方法で解決できることを知っていますが、bashビルトインのみを使用してそれを行う方法があるかどうか、そしてそうでない場合、それを行う最も効率的な方法は何かと思います。
次のような内容のファイルがあります
_AAA
B C DDD
FOO BAR
_
つまり、複数の行があり、各行にスペースがある場合とない場合があります。次のようなコマンドを実行したい
_cmd AAA "B C DDD" "FOO BAR"
_
cmd $(< file)
を使用すると、
_cmd AAA B C DDD FOO BAR
_
cmd "$(< file)"
を使用すると、
_cmd "AAA B C DDD FOO BAR"
_
各行に正確に1つのパラメーターを処理させるにはどうすればよいですか?
bash
でこれを行う正規の方法は次のようなものです
_unset args
while IFS= read -r line; do
args+=("$line")
done < file
cmd "${args[@]}"
_
または、bashのバージョンにmapfile
がある場合:
_mapfile -t args < filename
cmd "${args[@]}"
_
Mapfileとwhile-readループとワンライナーの違いを見つけることができる唯一の違い
_(set -f; IFS=$'\n'; cmd $(<file))
_
前者は空白行を空の引数に変換しますが、ワンライナーは空白行を無視します。この場合、ワンライナーの動作はとにかく私が好むものなので、コンパクトであることの2倍のボーナスです。
私はIFS=$'\n' cmd $(<file)
を使用しますが、_IFS=$'\n'
_が有効になる前に$(<file)
がコマンドラインを形成すると解釈されるため、機能しません。
私の場合は機能しませんが、多くのツールがnull (\000)
ではなくnewline (\n)
で行の終了をサポートしていることを学びました。たとえば、これらの状況の一般的な原因であるファイル名:
_find / -name '*.config' -print0 | xargs -0 md5
_
完全修飾ファイル名のリストを、グロビングや補間などを行わずにmd5の引数としてフィードします。それが非組み込みソリューションにつながります
_tr "\n" "\000" <file | xargs -0 cmd
_
ただし、空白のみの行はキャプチャされますが、これも空行を無視します。
ポータブル:
set -f # turn off globbing
IFS='
' # split at newlines only
cmd $(cat <file)
unset IFS
set +f
または、サブシェルを使用してIFS
およびオプションをローカルに変更します。
( set -f; IFS='
'; exec cmd $(cat <file) )
シェルは、二重引用符で囲まれていない変数またはコマンド置換の結果に対してフィールド分割とファイル名生成を実行します。したがって、set -f
を使用してファイル名の生成をオフにし、IFS
を使用してフィールド分割を設定して、改行だけを別々のフィールドにする必要があります。
Bashやkshの構造で得られるものはそれほど多くありません。 IFS
を関数に対してローカルにすることはできますが、set -f
をローカルにすることはできません。
Bashまたはksh93では、フィールドを複数のコマンドに渡す必要がある場合、フィールドを配列に格納できます。アレイを構築するときに拡張を制御する必要があります。次に、"${a[@]}"
は、Wordごとに1つずつ、配列の要素に展開されます。
set -f; IFS=$'\n'
a=($(cat <file))
set +f; unset IFS
cmd "${a[@]}"
これは一時的な配列で行うことができます。
$ cat input
AAA
A B C
DE F
$ cat t.sh
#! /bin/bash
echo "$1"
echo "$2"
echo "$3"
$ IFS=$'\n'; set -f; foo=($(<input))
$ for a in "${foo[@]}" ; do echo "--" "$a" "--" ; done
-- AAA --
-- A B C --
-- DE F --
$ ./t.sh "${foo[@]}"
AAA
A B C
DE F
その一時変数なしでそれを行う方法を理解することはできません-IFS
の変更がcmd
にとって重要ではない場合を除いて:
$ IFS=$'\n'; set -f; cmd $(<input)
それを行う必要があります。
Bash組み込みmapfile
を使用して、ファイルを配列に読み込むことができます
mapfile -t foo < filename
cmd "${foo[@]}"
または、未テストの場合、xargs
が実行する可能性があります
xargs cmd < filename
改行でファイルを分割するための最も基本的なループ(ポータブル)は次のとおりです。
#!/bin/sh
while read -r line; do # get one line (\n) at a time.
set -- "$@" "$line" # store in the list of positional arguments.
done <infile # read from a file called infile.
printf '<%s>' "$@" ; echo # print the results.
どちらが印刷されます:
$ ./script
<AAA><A B C><DE F>
はい、デフォルトのIFS =spacetabnewline。
機能する理由
IFS
を変更する必要はありません。set -f
必要です。-r
(raw)オプションは、ほとんどのバックスラッシュの削除を回避するためのものです。notは、スプリットやグロビングが必要な場合に機能します。このような場合、より複雑な構造が必要になります。
(まだ移植可能)が必要な場合:
IFS= read -r line
IFS=':' read -r a b c
。fileを他の文字で分割します(移植不可、ksh、bash、zshで動作):
IFS=':' read -d '+' -r a b c
もちろん、質問のタイトルは、スペースでの分割を避けて、コマンド実行を改行で分割することです。
シェルから分割する唯一の方法は、引用符なしで展開を残すことです:
echo $(< file)
これはIFSの値によって制御され、引用符で囲まれていない展開では、グロビングも適用されます。これを機能させるには、次のものが必要です。
シェルグロビングオプションの設定を解除しますset +f
:
set + f IFS = '' cmd $(<file)
もちろん、それによってIFSの値と残りのスクリプトのグロビングの値が変更されます。
old=$IFS
IFS=' #newline
'
array=`cat Submissions` #input the text in this variable
for ... #use parts of variable in the for loop
...
done
IFS=$old
私が見つけた最良の方法。うまくいきます。