web-dev-qa-db-ja.com

コマンド置換:改行ではなく、スペースで分割

私はこの問題をいくつかの方法で解決できることを知っていますが、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つのパラメーターを処理させるにはどうすればよいですか?

30
Old Pro

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
_

ただし、空白のみの行はキャプチャされますが、これも空行を無視します。

8
Old Pro

ポータブル:

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) 

それを行う必要があります。

10
Mat

Bash組み込みmapfileを使用して、ファイルを配列に読み込むことができます

mapfile -t foo < filename
cmd "${foo[@]}"

または、未テストの場合、xargsが実行する可能性があります

xargs cmd < filename
3
glenn jackman

ファイル

改行でファイルを分割するための最も基本的なループ(ポータブル)は次のとおりです。

#!/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を使用して、入力をseveral変数に分割します。 one変数しかないため、シェルによる分割は行われません。したがって、IFSを変更する必要はありません。
  • はい、先頭と末尾のスペース/タブは削除されていますが、この場合は問題ありません。
  • いいえ、展開されていないnquotedであるため、globbingは実行されません。したがって、set -f必要です。
  • 使用される(または必要とされる)唯一の配列は、配列のような定位置パラメーターです。
  • -r(raw)オプションは、ほとんどのバックスラッシュの削除を回避するためのものです。

notは、スプリットやグロビングが必要な場合に機能します。このような場合、より複雑な構造が必要になります。

(まだ移植可能)が必要な場合:

  • 先頭と末尾のスペース/タブの削除を避け、次を使用します:IFS= read -r line
  • ある文字の行をvarsに分割します。次を使用します:IFS=':' read -r a b c

fileを他の文字で分割します(移植不可、ksh、bash、zshで動作):

IFS=':' read -d '+' -r a b c

拡張

もちろん、質問のタイトルは、スペースでの分割を避けて、コマンド実行を改行で分割することです。

シェルから分割する唯一の方法は、引用符なしで展開を残すことです:

echo $(< file)

これはIFSの値によって制御され、引用符で囲まれていない展開では、グロビングも適用されます。これを機能させるには、次のものが必要です。

  • IFSを改行onlyに設定し、改行のみで分割します。
  • シェルグロビングオプションの設定を解除しますset +f

    set + f IFS = '' cmd $(<file)

もちろん、それによってIFSの値と残りのスクリプトのグロビングの値が変更されます。

0
Isaac
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

私が見つけた最良の方法。うまくいきます。

0
Rahul Bali