シェルでファイルを変数に読み込む方法は?


489

ファイルを読み取って変数に保存したいのですが、ファイルを出力するだけでなく、変数を保持する必要があります。これどうやってするの?私はこのスクリプトを書きましたが、それは私が必要とするものではありません:

#!/bin/sh
while read LINE  
do  
  echo $LINE  
done <$1  
echo 11111-----------  
echo $LINE  

スクリプトでは、ファイル名をパラメーターとして指定できるため、たとえばファイルに "aaaa"が含まれている場合、次のように出力されます。

aaaa
11111-----

しかし、これはファイルを画面に出力するだけなので、変数に保存したいと思います。これを行う簡単な方法はありますか?


1
プレーンテキストのようです。それはバイナリファイルだった場合、あなたが必要になり、これをした結果として、catまたは$(<someFile)(サイズが実際のファイルよりも小さい)、不完全な出力になります。
Aquarius Power

回答:


1052

クロスプラットフォームでsh使用する最小公分母:

#!/bin/sh
value=`cat config.txt`
echo "$value"

ではbashまたはzsh、起動せずに変数にファイル全体を読み取りますcat

#!/bin/bash
value=$(<config.txt)
echo "$value"

ファイルを呼び出すcatbashまたはzshファイルを丸呑みすることは、猫の無用な使用と見なされます。

改行を保持するためにコマンド置換を引用符で囲む必要はないことに注意してください。

参照:バッシュのハッカーのウィキ-コマンド置換-スペシャリティを


4
わかりましたが、shではなくbashです。すべてのケースに当てはまるとは限りません。
moala 2013

14
ではないだろうvalue="`cat config.txt`"value="$(<config.txt)"のconfig.txtにスペースが含まれている場合には、より安全なこと?
Martin von Wittich、2014

13
cat上記のように使用しても、の無用な使用とは限りませんcat。たとえば、< invalid-file 2>/dev/nullにルーティングできないエラーメッセージが表示されます/dev/nullが、にcat invalid-file 2>/dev/nullは正しくルーティングされ/dev/nullます。
Dejay Clayton 2016

16
私のような新しいシェルスクリプターの場合、猫のバージョンでは一重引用符ではなくバックティックを使用しています。うまくいけば、これにより誰かがそれを理解するのにかかった30分を節約できます。
エリクソンラ2017

7
私のような新しいbashersの場合:これvalue=$(<config.txt)は良いことですが、value = $(<config.txt)悪いことです。それらのスペースに注意してください。
ArtHare 2018年

88

ファイル全体を変数に読み込みたい場合:

#!/bin/bash
value=`cat sources.xml`
echo $value

行ごとに読みたい場合:

while read line; do    
    echo $line    
done < file.txt

2
@brain:ファイルがConfig.cppで、バックスラッシュが含まれている場合はどうなりますか。二重引用符と引用符?
user2284570 14年

2
で変数を二重引用符で囲む必要がありますecho "$value"。それ以外の場合、シェルは値に対して空白のトークン化とワイルドカード拡張を実行します。
tripleee 2016

3
@ user2284570 read -r単に代わりに使用してくださいread-常に、あなたが言及している奇妙なレガシー動作を特に必要としない限り。
tripleee 2016

74

2つの重要な落とし穴

これまでのところ他の回答では無視されていました:

  1. コマンド展開からの末尾の改行の削除
  2. NUL文字の削除

コマンド展開からの末尾の改行の削除

これは次の問題です。

value="$(cat config.txt)"

タイプソリューション。ただし、readベースソリューションでは使用できません。

コマンド展開により、末尾の改行が削除されます。

S="$(printf "a\n")"
printf "$S" | od -tx1

出力:

0000000 61
0000001

これは、ファイルから読み取る単純な方法を壊します。

FILE="$(mktemp)"
printf "a\n\n" > "$FILE"
S="$(<"$FILE")"
printf "$S" | od -tx1
rm "$FILE"

POSIXの回避策:コマンド展開に余分な文字を追加し、後で削除します。

S="$(cat $FILE; printf a)"
S="${S%a}"
printf "$S" | od -tx1

出力:

0000000 61 0a 0a
0000003

ほとんどPOSIXの回避策:ASCIIエンコード。下記参照。

NUL文字の削除

変数にNUL文字を格納するための健全なBashの方法はありません

これは、拡張と readソリューションのますが、適切な回避策はわかりません。

例:

printf "a\0b" | od -tx1
S="$(printf "a\0b")"
printf "$S" | od -tx1

出力:

0000000 61 00 62
0000003

0000000 61 62
0000002

ハ、私たちのNULはなくなった!

回避策:

  • ASCIIエンコード。下記参照。

  • bash拡張$""リテラルを使用します。

    S=$"a\0b"
    printf "$S" | od -tx1

    リテラルでのみ機能するため、ファイルからの読み取りには役立ちません。

落とし穴の回避策

uuencode base64エンコードバージョンのファイルを変数に格納し、使用する前にデコードします。

FILE="$(mktemp)"
printf "a\0\n" > "$FILE"
S="$(uuencode -m "$FILE" /dev/stdout)"
uudecode -o /dev/stdout <(printf "$S") | od -tx1
rm "$FILE"

出力:

0000000 61 00 0a
0000003

uuencodeとudecodeはPOSIX 7ですが、Ubuntu 12.04にはデフォルトでsharutilsはありません(パッケージ)... bashプロセスのPOSIX 7の代替案が表示されません<()別のファイルに書き込むことを除いて置換拡張機能の ...

もちろん、これは遅くて不便なので、本当の答えは、入力ファイルにNUL文字が含まれている可能性がある場合はBashを使用しないことです。


2
改行が必要だったので、これだけでうまくいきました。
Jason Livesay 2014

1
@CiroSantilli:FILEがConfig.cppで、バックスラッシュが含まれている場合はどうでしょうか。二重引用符と引用符?
user2284570 14年

@ user2284570知りませんでしたが、簡単に見つけることができますS="$(printf "\\\'\"")"; echo $S。出力:\'"。したがって、機能します=)
Ciro Santilli郝海东冠状病六四事件法轮機能

@CiroSantilli:5511行ですか?自動化された方法がないと確信していますか?
user2284570 2014年

@ user2284570わからない、5511行あるの?落とし穴は$()拡張に由来$()\'"ます。私の例では、拡張がで機能することを示しています。
Ciro Santilli郝海东冠状病六四事件法轮功


2

チロSantilliノートコマンド置換を使用しては、末尾の改行をドロップします。末尾の文字を追加する回避策は素晴らしいですが、かなり長い間使用した後、コマンド置換をまったく使用しないソリューションが必要だと判断しました。

私のアプローチでは、stdinの内容を変数に直接読み取るためにreadprintf組み込みの-vフラグと共に使用します。

# Reads stdin into a variable, accounting for trailing newlines. Avoids needing a subshell or
# command substitution.
read_input() {
  # Use unusual variable names to avoid colliding with a variable name
  # the user might pass in (notably "contents")
  : "${1:?Must provide a variable to read into}"
  if [[ "$1" == '_line' || "$1" == '_contents' ]]; then
    echo "Cannot store contents to $1, use a different name." >&2
    return 1
  fi

  local _line _contents
   while read -r _line; do
     _contents="${_contents}${_line}"$'\n'
   done
   _contents="${_contents}${_line}" # capture any content after the last newline
   printf -v "$1" '%s' "$_contents"
}

これは、末尾の改行の有無にかかわらず入力をサポートします。

使用例:

$ read_input file_contents < /tmp/file
# $file_contents now contains the contents of /tmp/file

すごい!_contents="${_contents}${_line}\n "改行を保存するようなものを使用しないのはなぜでしょうか。
イーノク

1
について質問してい$'\n'ますか?それが必要です。そうでなければ、リテラル\ n文字を追加します。コードブロックの最後にも余分なスペースがあり、それが意図的なものかどうかはわかりませんが、後続のすべての行に余分な空白をインデントします。
dimo414

さて、説明ありがとうございます!
イーノク

-3

forループで一度に1行にアクセスできます

#!/bin/bash -eu

#This script prints contents of /etc/passwd line by line

FILENAME='/etc/passwd'
I=0
for LN in $(cat $FILENAME)
do
    echo "Line number $((I++)) -->  $LN"
done

コンテンツ全体をファイル(たとえばline.sh)にコピーします。実行する

chmod +x line.sh
./line.sh

あなたのforループは行をループするのではなく、単語をループします。の場合、/etc/passwd各行に含まれる単語は1つだけです。ただし、他のファイルには1行に複数の単語が含まれている場合があります。
MPB
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.