web-dev-qa-db-ja.com

コマンド置換を使用すると改行文字が失われるのはなぜですか?

私はこのようなlinks.txtという名前のテキストファイルを持っています

link1
link2
link3

このファイルを1行ずつループして、すべての行で操作を実行したいと思います。 whileループを使用してこれを実行できることはわかっていますが、学習しているため、forループを使用することを考えました。私は実際にこのようなコマンド置換を使用しました

a=$(cat links.txt)

次に、このようなループを使用しました

for i in $a; do ###something###;done

また、私はこのようなことをすることができます

for i in $(cat links.txt); do ###something###; done

今私の質問は、変数aにcatコマンドの出力を代入すると、link1 link2とlink3の間の改行文字が削除され、スペースに置き換えられることです。

echo $a

出力

link1 link2 link3

そしてforループを使いました。コマンド置換を行うと、常に新しい行がスペースに置き換えられるのですか?

よろしく

35
user3138373

シェルがコマンドの置換後に フィールド分割 を実行したため、改行が失われました。

POSIX コマンド置換 セクション:

シェルは、サブシェル環境(シェル実行環境を参照)でコマンドを実行し、コマンド置換(コマンドのテキストと囲みの "$()"またはバッククォート)をコマンドの標準出力に置き換えて、コマンド置換を拡張し、削除します。置換の最後にある1つ以上の文字のシーケンス。 出力の最後の前に埋め込まれた文字は削除されません。ただし、IFSの値と有効な引用符に応じて、フィールド区切り文字として扱われ、フィールド分割時に削除される場合があります。出力にnullバイトが含まれている場合の動作は規定されていません。

デフォルトのIFS値(少なくともbash内):

$ printf '%q\n' "$IFS"
$' \t\n'

あなたの場合、IFSを設定したり、二重引用符を使用したりしないため、フィールド分割時に改行文字が削除されます。

改行を保存するには、たとえば、setting IFStoを空にします。

$ IFS=
$ a=$(cat links.txt)
$ echo "$a"
link1
link2
link3
28
cuonglm

改行は特殊文字であるため、いくつかの時点で入れ替えられます。それらを保持するには、引用符を使用して、それらが常に解釈されるようにする必要があります。

$ a="$(cat links.txt)"
$ echo "$a"
link1
link2
link3

ここで、データを操作するときは常に引用符を使用したため、改行文字(\n)は常にシェルによって解釈されるため、そのまま残りました。いつか使用するのを忘れると、これらの特殊文字は失われます。

スペースを含む行でループを使用する場合も、まったく同じ動作が発生します。たとえば、次のファイルがあるとします...

mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt

出力は、引用符を使用するかどうかによって異なります。

$ for i in $(cat links.txt); do echo $i; done
mypath1/file
with
spaces.txt
mypath2/filewithoutspaces.txt

$ for i in "$(cat links.txt)"; do echo "$i"; done
mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt

引用符を使用したくない場合は、シェルフィールドの区切り記号(IFS)を変更するために使用できる特別なシェル変数があります。この区切り文字を改行文字に設定すると、ほとんどの問題が解消されます。

$ IFS=$'\n'; for i in $(cat links.txt); do echo $i; done
mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt

完全を期すために、コマンド出力の置換に依存しない別の例を次に示します。しばらくして、readユーティリティの動作自体が原因で、ほとんどのユーザーはこの方法の方が信頼性が高いと考えていました。

$ cat links.txt | while read i; do echo $i; done

以下はreadのmanページからの抜粋です。

読み取りユーティリティは、標準入力から1行を読み取ります。

readは1行ずつ入力を取得するので、スペースが表示されても分割されません。パイプを介してcatの出力を渡すだけで、行を繰り返し処理します。

編集:他の回答やコメントから、catの使用に関して人々は非常に消極的であることがわかります。 jasonwryan がコメントで述べたように、シェルでファイルを読み取るよりproper方法は、ストリームリダイレクト(<)、 val0x00ffの回答はこちら で確認できます。しかし、質問は「シェルプログラミングでファイルを読み取る/処理する方法」ではないため、私の答えはより焦点を当てています引用行動ではなく、残りの行動。

40
John WH Smith

私の強調を加えるために、forループはwordsを反復します。あなたのファイルが:

one two
three four

次に、これはfour行を出力します。

for Word in $(cat file); do echo "$Word"; done

ファイルのlinesを反復するには、次のようにします。

while IFS= read -r line; do
    # do something with "$line" <-- quoted almost always
done < file
6
glenn jackman

Bashからreadを使用できます。 mapfileも探します

while read -r link
  do
   printf '%s\n' "$link"
  done < links.txt

またはmapfileを使用する

mapfile -t myarray < links.txt
for link in "${myarray[@]}"; do printf '%s\n' "$link"; done
4