コマンド出力を個々の行に分離する方法


12
list=`ls -a R*`
echo $list

シェルスクリプト内では、このechoコマンドは、現在のディレクトリからRで始まるすべてのファイルを1行で一覧表示します。各アイテムを1行に印刷するにはどうすればよいですか?

私は起こってすべてのシナリオのための一般的なコマンドを必要としlsdufind -type -dなど、


6
一般的な注意:これをlsで行わないでください奇妙なファイル名で壊れます。こちらをご覧ください
terdon 2017年

回答:


12

コマンドの出力に複数の行が含まれる場合は、変数を引用符で囲んで、エコー時にこれらの改行を保持します。

echo "$list"

そうしないと、シェルが変数の内容を展開して空白(スペース、改行、タブ)に分割し、それらは失われます。


15

不適切にls出力を変数に入れてそれを実行echoする代わりに、すべての色を削除する代わりに、

ls -a1

から man ls

       -1     list one file per line.  Avoid '\n' with -q or -b

lsを表示する以外は、の出力については何もしないことをお勧めします:)

たとえば、シェルグロブとforループを使用して、ファイルで何かを行います...

shopt -s dotglob                  # to include hidden files*

for i in R/*; do echo "$i"; done

* ただし、これには現在のディレクトリ.またはその親は含まれません..


1
:私は交換することをお勧めしたいecho "$i"printf "%s\n" "$i" (1ファイル名が始まる場合窒息しないことを「 - 」)。実際、ほとんどの場合echo somethingはに置き換える必要がありprintf "%s\n" "something"ます。
Olivier Dulac 2017年

2
@OlivierDulacそれらはすべてRここから始まりますが、一般的にはそうです。しかし、スクリプトを作成する場合でも、常に先行-するパスに対応しようすると問題が発生します。人々は--、一部のコマンドのみがサポートするがオプションの終わりを意味すると想定します。がサポートしていないfind . [tests] -exec [cmd] -- {} \;場所を確認[cmd]しました--。そこにあるすべての道は.とにかく始まります!しかし、それは交換に対しては、引数ませんechoprintf '%s\n'ここでは、ループ全体をに置き換えることができます printf '%s\n' R/*。Bashはprintf組み込みとして持っているので、引数の数/長さに事実上制限はありません。
Eliah Kagan

12

@muruが提案したように引用符で囲むと、実際に要求したとおりに機能ますが、このために配列を使用することも検討してください。例えば:

IFS=$'\n' dirs=( $(find . -type d) )

IFS=$'\n'唯一の配列の各要素を取得改行characcters Oの出力を分割するためにはbashに指示します。これがないと、スペースで分割さa file name with spaces.txtれるため、1つの要素ではなく5つの別々の要素になります。\nただし、ファイル/ディレクトリ名に改行()を含めることができる場合、このアプローチは機能しません。コマンドの出力の各行を配列の要素として保存します。

古いスタイル`command`$(command)優先構文に変更したことに注意してください。

これで、という配列があり$dirs、各bofの要素は、前のコマンドの出力の行です。例えば:

$ find . -type d
.
./olad
./ho
./ha
./ads
./bar
./ga
./da
$ IFS=$'\n' dirs=( $(find . -type d) )
$ for d in "${dirs[@]}"; do
    echo "DIR: $d"
  done
DIR: .
DIR: ./olad
DIR: ./ho
DIR: ./ha
DIR: ./ads
DIR: ./bar
DIR: ./ga
DIR: ./da

ここで、bashの奇妙さ(ここを参照)のIFSために、これを実行した後、元の値にリセットする必要があります。それで、それを保存して再署名します:

oldIFS="$IFS"
IFS=$'\n' dirs=( $(find . -type d) ) 
IFS="$oldIFS"

または手動で行う:

IFS=" "$'\t\n '

または、現在のターミナルを閉じるだけです。新しいものには、元のIFSが再び設定されます。


私はこのようなものを提案したかもしれませんが、duOPの見栄えを良くするための部分は、出力形式を保持することにもっと関心があります。
ムル

ディレクトリ名にスペースが含まれている場合、これをどのように機能させることができますか?
デザート

@デザートおっと!スペース(改行ではない)で動作するはずです。指摘してくれてありがとう。
terdon 2017年

1
アレイの割り当て後にIFSをリセットまたは設定解除する必要があることに注意してください。unix.stackexchange.com/q/264635/70524
muru

@muruあらすごい、ありがとう、その奇妙さを忘れていた。
terdon 2017年

2

出力を格納する必要あり、配列が必要な場合は、mapfileそれを簡単にします。

まず、コマンドの出力をまったく保存する必要があるかどうかを検討します。そうでない場合は、コマンドを実行してください。

コマンドの出力を行の配列として読み取る場合は、グロビングを無効にし、IFS行で分割するように設定し、( )配列作成構文内でコマンド置換を使用し、IFS後でリセットするのが1つの方法であることは事実です。これはより複雑であるそれはそうより。terdonの答えは、そのアプローチの一部をカバーしています。ただし、代わりに、テキストを行の配列として読み取るためmapfileのBashシェルの組み込みコマンドであるを使用することをお勧めします。次に、ファイルから読み取る単純なケースを示します。

mapfile < filename

これは、行をと呼ばれる配列に読み込みますMAPFILE入力のリダイレクトが なければ、で指定されたファイルの代わりに、シェルの標準入力(通常は端末)から読み取ることになります。デフォルト以外の配列に読み込むには、その名前を渡します。たとえば、これは配列に読み込みます:< filenamefilenameMAPFILElines

mapfile lines < filename

変更を選択できる別のデフォルトの動作は、行末の改行文字がそのまま残されることです。それらは各配列要素の最後の文字として表示されます(ただし、入力が改行文字なしで終了した場合を除きます。この場合、最後の要素には改行文字がありません)。これらの改行を短くして配列に表示されないようにするには、-tオプションをに渡しますmapfile。たとえば、これは配列recordsを読み取り、末尾の改行文字を配列要素に書き込みません。

mapfile -t records < filename

-t配列名を渡さずに使用することもできます。つまり、の暗黙の配列名でMAPFILEも機能します。

mapfileシェル組み込みは、他のオプションをサポートしており、それが、代わりとして呼び出すことができますreadarray。詳細についてはhelp mapfile、実行(およびhelp readarray)してください。

ただし、ファイルから読み取るのではなく、コマンドの出力から読み取る必要があります。そのためには、プロセス置換を使用します。このコマンドは、コマンドから行を読み込むsome-commandarguments...、それらにそのコマンドライン引数や場所などmapfileのデフォルトのアレイMAPFILEその終端の改行文字では、削除しました:

mapfile -t < <(some-command arguments...)

プロセスの置換は、runningの出力を読み取ることができる実際のファイル名に置き換えられます。ファイルは通常のファイルではなく名前付きパイプであり、Ubuntuでは(場合によっては以外の番号が付けられる)という名前になりますが、シェルが処理するため、詳細を気にする必要はありません。すべての舞台裏。<(some-command arguments...)some-command arguments.../dev/fd/6363

代わりにを使用してプロセスの置換を省略できると思うかもしれませんが、で区切られた複数のコマンドのパイプラインがある場合、Bashランはすべてのコマンドをサブシェルで実行するため、機能しません。したがって、両方のと、自分の中で実行する環境から初期化していますが、パイプラインを実行したシェルの環境から分離しています。が実行されるサブシェルでは、配列にデータ入力されますが、コマンドが終了するとその配列は破棄されます。呼び出し元に対して作成または変更されることはありません。some-command arguments... | mapfile -t|some-command arguments...mapfile -tmapfile -tMAPFILEMAPFILE

terdonの回答のは、次のようになりますmapfile

mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
    echo "DIR: $d"
done

それでおしまい。が設定されているかどうかを確認し、IFS設定されているかどうかとその値を追跡し、改行に設定して、後でリセットまたは再設定解除する必要はありません。あなたは無効にする必要はありませんグロブ(例えば、でset -f) -あなたはファイル名が含まれているかもしれないので、真剣にそのメソッドを使用するようにしている場合は、本当に必要とされている*?[--then(それを再度有効set +f後で)。

また、特定のループを1つのprintfコマンドで完全に置き換えることもできます。ただし、これは実際にはの利点ではありません。配列を作成するために、または別の方法をmapfile使用するかどうかに関係なく実行できますmapfile。ここに短いバージョンがあります:

mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"

terdonが述べているように、行ごとの操作は常に適切であるとは限らず、改行を含むファイル名では正しく機能しないことに留意することが重要です。そのようにファイルに名前を付けることはお勧めしませんが、偶発的に発生する可能性があります

実際には万能のソリューションはありません。

「すべてのシナリオに対応する一般的なコマンド」と、mapfileいくらか使用するアプローチがその目標に近づくように求めましたが、要件を再検討することをお勧めします。

上記のタスクは、1つのfindコマンドだけで実現できます。

find . -type d -printf 'DIR: %p\n'

各行の先頭sedに追加DIR: するような外部コマンドを使用することもできます。これは間違いなくやや醜く、そのfindコマンドとは異なり、改行を含むファイル名内に追加の「プレフィックス」が追加されますが、入力に関係なく機能するため、要件を満たしているため、出力を変数または配列

find . -type d | sed 's/^/DIR: /'

リストを作成し、見つかった各ディレクトリに対してアクションを実行する必要がある場合(some-commandディレクトリのパスを実行して引数として渡すなど)も、findそれを行うことができます。

find . -type d -print -exec some-command {} \;

別の例として、各行に接頭辞を追加する一般的なタスクに戻りましょう。出力を表示したいhelp mapfileが、行に番号を付けたいとしましょう。私は実際にmapfileはこれを使用せず、それをシェル変数またはシェル配列に読み込む他のメソッドも使用しません。help mapfile | cat -n私が望むフォーマットを与えないと仮定すると、私は使うことができますawk

help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'

コマンドのすべての出力を単一の変数または配列に読み込むことは有用で適切な場合がありますが、大きな欠点があります。既存のコマンドまたは既存のコマンドの組み合わせが既に必要以上のことを既に実行している可能性がある場合に、シェルを使用するという複雑さに対処する必要があるだけでなく、コマンドの出力全体をメモリに保存する必要があります。それが問題ではないことを知っている場合もあれば、大きなファイルを処理している場合もあります。

一般的に試みられ、時には正しく行われる代替策read -rは、ループで入力を1行ずつ読み取ることです。後の行を操作するときに前の行を保存する必要がなく、長い入力を使用する必要がある場合は、よりも優れている場合がありますmapfile。ただし、ほとんどの場合、作業を実行できるコマンドにパイプで接続できる場合は、これも回避する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.