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

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

以下のようにlink.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

私の問題は、catコマンドの出力を変数aに置き換えると、link1 link2とlink3の間の新しい行文字が削除され、空白に置き換えられることです。

echo $a

出力

リンク1リンク2リンク3

その後、forループを使用しました。コマンド置換を実行すると、新しい行は常に空白に置き換えられますか?

挨拶

答え1

改行文字は特殊文字なので、ある時点で置き換えられます。これを保存するには、常に引用符を使用して解釈する必要があります。

$ 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以下は、マニュアルページから抜粋した内容です。

読み取りユーティリティは標準入力から1行を読み取る必要があります。

read入力は 1 行ずつ行われるため、スペースが現れるたびに入力は中断されません。出力をパイプで接続すると、cat行がうまく繰り返されます。

編集する:人々catジェイソン・ライアン彼のコメントにもっと適切シェルからファイルを読み取る方法は、<前述のようにストリームリダイレクト()を使用することです。val0x00ffの答えはここにあります。しかし、質問は「ではないからシェルプログラミングでファイルを読み取って処理する方法「私の答えは、残りのものよりも引用行為に焦点を当てています。

答え2

シェルがすでに実行されているため、改行文字が失われます。フィールド分割コマンドを置き換えた後。

POSIXではコマンドの置き換え部分:

シェルはサブシェル環境(シェル実行環境を参照)でコマンドを実行し、コマンド置換(コマンドテキスト+ "$()"またはバックティック)をコマンドの標準出力に置き換えてコマンド置換を拡張する必要があります。複数文字のシーケンスです。含まれている文字は、出力が終了する前に削除してはいけません。ただし、IFS値と有効な参照によってはフィールド区切り文字として扱われ、フィールド分割中に削除される可能性があります。。出力に null バイトが含まれている場合、動作は指定されません。

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

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

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

たとえば、スペースに設定して改行を維持できますIFS

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

答え3

私のポイントを強調するためにforループが繰り返されます。性格。ファイルが次の場合:

one two
three four

これにより、次のものが発行されます。4つライン:

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

繰り返しワイヤーファイルで次の操作を行います。

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

答え4

改行文字はどのように機能するかによって空白に置き換えられますecho。引数をスペースに連結します。echoパラメータ区切り文字を空白に置き換えます。実際にfor必要なものは何でも繰り返すことができますが、まずフィールド区切り文字を指定する必要があります。

string=abababababababababababa IFS=a        
for c in $string
do printf %s "$c"
done

出力

bbbbbbbbbbb

ただし、これはループ固有の動作ではありませんfor。すべてのフィールド分割拡張で発生します。

printf %s $string
bbbbbbbbbbb

たとえば、ファイル内の空でない行の最初の10バイトのみを印刷したい場合。

###original:
first "line"
<second>"line"
<second>"line"
<second>line and so on%
(IFS='                                                       
'; printf %.10s\\n $(cat file))
###output
first "lin
<second>"l
<second>"l
<second>li

私が明示した理由は空白ではない\n-ewlineはです$IFS。 2つ以上が連続して発生する場合、他のすべての項目は空の引数を提供しますが、一連のスペース、タブ、または改行は単一のフィールドとしてのみ計算されます。

たとえば、

(IFS=0;printf 'ten lines!%s\n' $(printf "%010d"))

ten lines!
ten lines!
ten lines!
ten lines!
ten lines!
ten lines!
ten lines!
ten lines!
ten lines!
ten lines!

しかし...

(IFS=\ ;printf 'one line%s\n' $(printf "%010s"))
one line

どちらの場合も、printf10個のパディング文字が印刷されます。最初のケースでは10個のゼロが印刷され、2番目の場合は10個のスペースが印刷されます。最初のケースでは、各ゼロは空のフィールドを生成し、2番目のケースではprintf10個の空の引数を取得します。各引数に対応する書式文字列が書き込まれますが、2番目の場合はすべての空白がまったく意味なく印刷されます。

そうでないことに注意してください。ただシェルは引用符なしの拡張を使用してフィールドタイプを生成します。基本的に全体的な状況。次のようにしてください。

for line in $(cat file)

これは、一部の行に実際のファイルと一致するシェルグローバル変数が含まれる可能性が高いため、非常に予期しない結果を招く可能性があり、突然$line入力行が参照されなくなり、むしろディスク上のファイル名が参照されます。

$IFS分割に使用する計画の場合は、次のようになります。いつも良い考えは次のとおりです。

set -f

...まず、globを実行するときにシェルにglobを実行しないように指示します。完了したら、を使用して再度有効にできますset +f

関連情報