変数に値を割り当てる行ごとにファイルを読み取ります


753

次の.txtファイルがあります。

Marco
Paolo
Antonio

行ごとに読みたいので、各行に.txt行の値を変数に割り当てます。私の変数が$nameであるとすると、フローは次のとおりです。

  • ファイルから最初の行を読み取る
  • 割り当て$name= "マルコ"
  • いくつかのタスクを行う $name
  • ファイルから2行目を読み取る
  • 割り当て$name= "Paolo"


3
それらの質問はどういうわけかマージできるのでしょうか?どちらにも問題のさまざまな側面を強調するいくつかの本当に良い答えがあり、悪い答えはコメントに詳細な説明があり、何が悪いのか、そして今のところ、検討すべきことについての全体的な概観を得ることができませんペアから1つの質問。2ページ以上に分割するのではなく、すべてを1つの場所に配置すると便利です。
Egor Hans

回答:


1357

次の例では、引数として渡されたファイルを1行ずつ読み取ります。

while IFS= read -r line; do
    echo "Text read from file: $line"
done < my_filename.txt

これは、ループ内のファイルから行を読み取るための標準形式です。説明:

  • IFS=(またはIFS='')先頭/末尾の空白が削除されないようにします。
  • -r バックスラッシュエスケープが解釈されないようにします。

または、bashファイルのヘルパースクリプトに、例の内容を入れます。

#!/bin/bash
while IFS= read -r line; do
    echo "Text read from file: $line"
done < "$1"

上記をfilename readfileでスクリプトに保存すると、次のように実行できます。

chmod +x readfile
./readfile filename.txt

ファイルが標準のPOSIXテキストファイルでない場合(=改行文字で終了していない場合)、ループを変更して、後続の部分的な行を処理できます。

while IFS= read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < "$1"

ここで|| [[ -n $line ]]は、最後の行がaで終わっていない場合に無視されないようにします\nreadEOFが検出されると、ゼロ以外の終了コードが返されるため)。

ループ内のコマンドも標準入力から読み取る場合、によって使用されるファイル記述子は、read他の何かに出くわす可能性があります(標準ファイル記述子を避けます)。たとえば、

while IFS= read -r -u3 line; do
    echo "Text read from file: $line"
done 3< "$1"

(非Bashシェルは知らないかもしれませんread -u3; read <&3代わりに使用してください。)


23
この方法には注意点があります。whileループ内の何かがインタラクティブである場合(たとえば、stdinから読み取る)、$ 1から入力を取得します。手動でデータを入力する機会は与えられません。
カルピー、2014年

10
注意-一部のコマンドはこれを壊します(ループを壊します)。たとえばssh-nフラグを指定しないと、効果的にループを脱出できます。これには十分な理由があると思いますが、コードが失敗する原因を特定するのにしばらく時間がかかりました。
Alex

6
ワンライナーとして:IFS = ''の読み取り-r line || [[-n "$ line"]]; "$ line"をエコーし​​ます。完了<ファイル名
ジョセフジョンソン

8
@OndraŽižka、それはffmpeg消費stdin が原因です。ラインに追加</dev/nullするffmpegと、ループができなくなります。またはループに代替FDを使用します。その「代替FD」アプローチは次のようになりwhile IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1"ます。
Charles Duffy

9
grumble re:.sh拡張機能についてアドバイスします。UNIXの実行可能ファイルには通常、拡張機能はありません(実行しませんls.elf)。また、bashシバン(およびなどのbash専用ツール[[ ]])とPOSIX sh互換性を意味する拡張機能は、内部的に矛盾しています。
Charles Duffy

309

以下を表す-rフラグを使用することをお勧めしますread

-r  Do not treat a backslash character in any special way. Consider each
    backslash to be part of the input line.

から引用していman 1 readます。

もう1つは、ファイル名を引数として受け取ることです。

ここに更新されたコードがあります:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"

4
行の先頭と末尾のスペースを
削除

@トーマス、そして真ん中のスペースはどうなりますか?ヒント:不要なコマンド実行の試み。
kmarsh 2016

1
これは、受け入れられた答えとは対照的に、私にとってはうまくいきました。
神経伝達物質2016年

3
@TranslucentCloud、これが機能し、受け入れられた回答が機能しなかった場合、私はあなたのシェルがそうshではなかったと疑っていますbash|| [[ -n "$line" ]]受け入れられた回答の構文で使用されている拡張テストコマンドはバシズムです。とは言っても、その構文には実際に適切な意味があります。これにより、改行がなくても入力ファイルの最後の行までループが継続されます。これをPOSIX準拠の方法で実行したい場合は|| [ -n "$line" ][ではなくを使用し[[ます。
Charles Duffy、

3
それは、これは、言わまだセットに変更する必要があるIFS=ためにread空白をトリミング防ぐために。
Charles Duffy

132

次のBashテンプレートを使用すると、ファイルから一度に1つの値を読み取って処理できるはずです。

while read name; do
    # Do what you want to $name
done < filename

14
ワンライナーとして:名前を読みながら; $ {name}をエコーし​​ます。完了<ファイル名
Joseph Johnson

4
@CalculusKnight、テストするのに十分な興味深いデータを使用しなかったため、「動作」しただけです。バックスラッシュを含むコンテンツ、またはのみを含む行を試してください*
Charles Duffy

7
@Matthias、結局は偽であることが判明する仮定は、セキュリティに影響するかどうかにかかわらず、バグの最大の原因の1つです。私がこれまでに見た最大のデータ損失イベントは、誰かが「文字通り決して起こらない」と想定したシナリオによるものでした-バッファオーバーフローにより、ランダムなメモリがファイルの名前付けに使用されるバッファにダンプされ、スクリプトは、どの名前がこれまでにある可能性があるかを推測しました。非常に、持って起こる非常に不幸な行動を。
Charles Duffy

5
@Matthias、...そしてここでも特に当てはまります。StackOverflowで示されているコードサンプルは、人々が自分の作業でパターンを再利用するための教育ツールとして使用されることを意図しているためです!
Charles Duffy、

5
@Matthias、私は「期待するデータのためだけにコードを考案するべきだ」という主張に全く反対します。予期しないケースとは、バグが存在する場所、セキュリティの脆弱性が存在する場所です。それらを処理することが、slapdashコードと堅牢なコードの違いです。当然のことながら、その処理は派手である必要はありません。単に「エラーで終了」することもできますが、処理がまったくない場合、予期しないケースでの動作は未定義です。
Charles Duffy 2017年

76
#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done

8
他の回答に対しては何もありません。おそらくより洗練されているかもしれませんが、シンプルで読みやすく、必要なもので十分なので、この回答を賛成します。機能するためには、読み込まれるテキストファイルは空白行で終了する必要があります(つまりEnter、最後の行の後に押す必要があります)。そうでない場合、最後の行は無視されます。少なくともそれが私に起こったことです。
アントニオヴィニシウスメネゼスメデイ

12
猫の無用な使い方は絶対に?
Brian Agnew

5
そして、引用は壊れています。大文字の変数名はシステムで使用するために予約されているため、使用しないでください。
Tripleee、2016年

7
@AntonioViniciusMenezesMedei、...さらに、私は人々がこれらの警告が彼らにとって重要ではないと仮定したので経済的損失を被るのを見てきました。良い習慣を学ぶことができませんでした。そして、重要な請求データのバックアップを管理するスクリプトを作成するときに慣れていた習慣に従いました。正しいことを学ぶことは重要です。
Charles Duffy

6
ここでのもう1つの問題は、パイプが新しいサブシェルを開くことです。つまり、ループの終了後にループ内に設定されたすべての変数を読み取ることができません。
mxmlnkn 2017

20

多くの人々が過度に最適化されたソリューションを投稿しています。私はそれが間違っているとは思いませんが、これがどのように機能しているかを誰もが簡単に理解できるようにするには、あまり最適化されていないソリューションが望ましいと思います。これが私の提案です:

#!/bin/bash
#
# This program reads lines from a file.
#

end_of_file=0
while [[ $end_of_file == 0 ]]; do
  read -r line
  # the last exit status is the 
  # flag of the end of file
  end_of_file=$?
  echo $line
done < "$1"

20

使用する:

filename=$1
IFS=$'\n'
for next in `cat $filename`; do
    echo "$next read from $filename" 
done
exit 0

設定がIFS異なると、奇妙な結果になります。


34
これは恐ろしい方法です。気づく前に発生するグロビングの問題が発生したくない場合以外は、使用しないでください。
gniourf_gniourf 2014年

13
@MUYBelgiumは、1 *行に1つを含むファイルで試しましたか?とにかく、これはアンチパターンです。forで行を読み取らないでください
gniourf_gniourf

2
@OndraŽižka、このreadアプローチは、コミュニティコンセンサスによるベストプラクティスアプローチです。コメントで言及した警告は、ループがffmpegstdinから読み取るコマンド(など)を実行するときに適用されるものであり、ループに非stdin FDを使用するか、そのようなコマンドの入力をリダイレクトすることで簡単に解決されます。対照的に、for-loopアプローチのグロビングバグを回避することは、シェルグローバル設定の変更を行う(そして元に戻す必要がある)ことを意味します。
Charles Duffy

1
OndraŽižka@、...さらに、forあなたは、あなたがた場合でもデータのギガバイトを超えるループしている場合、それは完全に使用できなくなり、ここですべてのコンテンツがループを全く実行を開始する前に読まれなければならないことを意味を使用したループアプローチ持ち無効をグロビング; while readループは、サブプロセスの生成コンテンツがまだ実行されている間(このような目的をストリーミングするために使用可能である)の実行を開始できることを意味し、現時点では複数の単一ラインのデータよりも記憶する必要がなく、また、制限されたメモリの消費量を有しています。
Charles Duffy

1
実際、whileベースのアプローチでも*文字の問題があるようです。上記の受け入れられた回答のコメントを参照してください。ただし、アンチパターンであるファイルの反復については議論しません。
Egor Hans

9

入力ファイルとユーザー入力の両方(またはstdinからの何か)を処理する必要がある場合は、次のソリューションを使用します。

#!/bin/bash
exec 3<"$1"
while IFS='' read -r -u 3 line || [[ -n "$line" ]]; do
    read -p "> $line (Press Enter to continue)"
done

受け入れられた回答bash-hackersリダイレクトチュートリアルに基づいてます。

ここでは、スクリプト引数として渡されたファイルのファイル記述子3を開き、readこの記述子を入力として使用するように指示します(-u 3)。したがって、ユーザー入力を読み取れるように、端末または別の入力ソースに接続されたデフォルトの入力記述子(0)をそのままにします。


7

適切なエラー処理:

#!/bin/bash

set -Ee    
trap "echo error" EXIT    
test -e ${FILENAME} || exit
while read -r line
do
    echo ${line}
done < ${FILENAME}

説明を追加していただけませんか?
タイラークリスチャン

残念ながら、ファイルの最後の行が欠落しています。
ungalcrys

...また、引用符が不足しているため、ワイルドカードを含む行を変更します-BashPitfalls#14で説明されています。
Charles Duffy、

0

以下はファイルの内容を出力するだけです:

cat $Path/FileName.txt

while read line;
do
echo $line     
done

1
この回答は実際には既存の回答に何も追加されず、タイプミス/バグが原因で機能せず、多くの点で機能しません。
Konrad Rudolph、

0

bashでIFS(内部フィールドセパレーター)ツールを使用し、行をトークンに分割するために使用する文字を定義します。デフォルトでは、< tab > / < space > / < newLine >が含まれます

ステップ1:ファイルデータを読み込み、リストに挿入します。

# declaring array list and index iterator
declare -a array=()
i=0

# reading file in row mode, insert each line into array
while IFS= read -r line; do
    array[i]=$line
    let "i++"
    # reading from file path
done < "<yourFullFilePath>"

ステップ2:出力を反復して出力します。

for line in "${array[@]}"
  do
    echo "$line"
  done

配列内の特定のインデックスをエコー:配列内の変数へのアクセス:

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