「cat << EOF」はbashでどのように機能しますか?


631

プログラムに複数行入力を入力するスクリプトを作成する必要がありました(psql)。

少しグーグルで調べたところ、次の構文が機能することがわかりました。

cat << EOF | psql ---params
BEGIN;

`pg_dump ----something`

update table .... statement ...;

END;
EOF

これにより、複数行の文字列(fromからBEGIN;END;含む)が正しく構築され、入力としてにパイプされますpsql

しかし、私はそれがどのように/なぜ機能するのか分かりません、誰かが説明できますか?

私はを主に参照してるcat << EOF私が知っている、>ファイルへの出力>>ファイルに追加、<ファイルからの入力を読み込みます。

<<正確には何をしますか?

そして、そのためのマニュアルページはありますか?


26
それはおそらく役に立たない使用法ですcat。試してみてくださいpsql ... << EOF ... また、「ここに文字列」を参照してくださいします。mywiki.wooledge.org/BashGuide/InputAndOutput?#Here_Strings
通知があるまで

1
私はそれが猫では機能するがエコーでは機能しないことに驚いています。catは、ファイル名をchar文字列ではなくstdinとして想定する必要があります。psql << EOFは論理的に聞こえますが、そうではありません。猫では機能しますが、エコーでは機能しません。奇妙な行動。それについての手がかりはありますか?
Alex

自分への回答:パラメーターなしのcatは、入力(stdin)を介して送信されたものを実行し、出力に複製します。したがって、その出力を使用して、>を介してファイルに入力します。実際、パラメーターとして読み取られるファイル名はstdinストリームではありません。
Alex

@Alex echoは、stdingをcat読み取るとき(パイプでパイプラインに接続するとき)、またはコマンドライン引数に対応するファイルを読み取るときに、コマンドライン引数を出力するだけです
The-null-Pointer-

回答:


519

これはヒアドキュメント形式と呼ばれ、stdinに文字列を提供します。詳細については、https://en.wikipedia.org/wiki/Here_document#Unix_shellsを参照してください。


からman bash

ヒアドキュメント

このタイプのリダイレクトは、単語(末尾の空白なし)のみを含む行が見つかるまで、現在のソースから入力を読み取るようにシェルに指示します。

その時点までに読み取られたすべての行は、コマンドの標準入力として使用されます。

ヒアドキュメントの形式は次のとおりです。

          <<[-]word
                  here-document
          delimiter

wordに対して、パラメーター展開、コマンド置換、算術展開、またはパス名展開は実行されません 。wordのいずれかの文字が引用符で囲まれている場合、 区切り文字wordの引用符の削除の結果であり、ヒアドキュメントの行は展開されません。wordが引用符で囲まれていない場合、ヒアドキュメントのすべての行は、 パラメータ展開、コマンド置換、および算術展開の対象になります。後者の場合、文字列は\<newline>無視され、\文字を引用するのに使用されなければならない\$`

リダイレクト演算子がの場合、<<-先頭のすべてのタブ文字は、入力行と区切り文字を含む行から削除されます。これにより、シェルスクリプト内のヒアドキュメントを自然な方法でインデントできます。


12
変数/パラメーターの展開を無効にするのが一番大変でした。私がしなければならなかったすべては「二重引用符」を使用することでしたそしてそれはそれを修正しました!情報をありがとう!
Xeoncross、2011年

11
<<-のみリードすることにしてくださいノートタブのないソフトタブ文字-文字が削除されます。これは、実際にタブ文字が必要な場合のまれなケースの1つです。ドキュメントの残りの部分でソフトタブを使用している場合は、非表示の文字を表示し、(たとえば)タブ文字をコピーして貼り付けてください。これを正しく行うと、構文の強調表示で終了区切り文字を正しくキャッチできるはずです。
trkoch 2015年

1
この回答が以下の回答よりもどのように役立つかわかりません。他の場所(すでにチェックされている可能性が高い)で見つけられる情報を単に逆流するだけです
BrDaHa

@BrDaHa、多分そうではありません。なんで聞くの?賛成票のため?それ数年間で唯一のものでした。日付を比較するとわかります。
Alexei Martianov

501

cat <<EOFbashで複数行のテキスト、例えばで作業するときの構文は非常に便利です。複数行の文字列をシェル変数、ファイル、またはパイプに割り当てる場合。

cat <<EOFBash での構文の使用例:

1.複数行の文字列をシェル変数に割り当てる

$ sql=$(cat <<EOF
SELECT foo, bar FROM db
WHERE foo='baz'
EOF
)

これで、$sql変数に改行文字も保持されます。で確認できecho -e "$sql"ます。

2.複数行の文字列をBashのファイルに渡す

$ cat <<EOF > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
EOF

print.shファイルは現在含まれています:

#!/bin/bash
echo $PWD
echo /home/user

3.複数行の文字列をBashのパイプに渡す

$ cat <<EOF | grep 'b' | tee b.txt
foo
bar
baz
EOF

b.txtファイルが含まれているbarbazのライン。同じ出力がに出力されstdoutます。


1. 1と3は猫なしで行うことができます。2.例1は、単純な複数行の文字列で実行できます
Daniel Alder

269

あなたの場合、「EOF」は「ヒアタグ」として知られています。基本的に<<Hereは、「タグ」まで複数行の文字列を入力することをシェルに伝えますHere。あなたが好きなあなたは、それはしばしばだ、このタグに名前を付けることができますEOFSTOP

Hereタグに関するいくつかのルール:

  1. タグは大文字または小文字の任意の文字列にすることができますが、ほとんどの人は慣習的に大文字を使用します。
  2. その行に他の単語がある場合、タグはヒアタグとは見なされません。この場合、文字列の一部と見なされます。タグと見なされるには、タグ自体が別の行になければなりません。
  3. タグと見なされるためには、その行の先頭または末尾にスペースがあってはなりません。それ以外の場合は、文字列の一部と見なされます。

例:

$ cat >> test <<HERE
> Hello world HERE <-- Not by itself on a separate line -> not considered end of string
> This is a test
>  HERE <-- Leading space, so not considered end of string
> and a new line
> HERE <-- Now we have the end of the string

31
これが実際の最良の答えです...両方を定義し、関連する理論ではなく使用の主な目的を明確に述べます...重要ですが必要ではありません...感謝-超役立つ
oemb1905

5
@edelansを追加する必要があり<<-ます。先頭のタブを使用しても、タグの認識が妨げられることはありません
The-null-Pointer-

1
あなたの答えは「あなたは複数行の文字列を入力するつもりです」で私をクリックしました
微積分

79

POSIX 7

kennytmが引用していますがman bash、そのほとんどはPOSIX 7でもあります:http : //pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04 :

リダイレクト演算子「<<」と「<<-」はどちらも、「here-document」と呼ばれるシェル入力ファイルに含まれる行をコマンドの入力にリダイレクトできます。

here-documentは、次の単語の後に始まり、区切り文字とaのみを含み、間に文字がない行があるまで続く単一の単語として扱われます。次に、次のヒアドキュメントがあれば開始します。形式は次のとおりです。

[n]<<word
    here-document
delimiter

ここで、オプションのnはファイル記述子番号を表します。番号を省略した場合、ヒアドキュメントは標準入力(ファイル記述子0)を参照します。

単語内のいずれかの文字が引用されている場合、区切り文字は単語に対して引用削除を実行することによって形成され、ヒアドキュメントの行は拡張されません。それ以外の場合、区切り文字は単語そのものでなければなりません。

単語内の文字が引用されていない場合、ヒアドキュメントのすべての行が、パラメータ展開、コマンド置換、および算術展開のために展開されます。この場合、入力内のは内部の二重引用符として動作します(二重引用符を参照)。ただし、二重引用符が「$()」、「」、または「$ {}」内にある場合を除いて、二重引用符文字( '"')はヒアドキュメント内では特別に扱われません。

リダイレクト記号が「<<-」の場合、すべての先行<tab>文字は、入力行と末尾の区切り文字を含む行から削除されます。1つの行に複数の「<<」または「<<-」演算子が指定されている場合、最初の演算子に関連付けられているhere-documentは、アプリケーションによって最初に提供され、シェルによって最初に読み取られます。

ヒアドキュメントが端末デバイスから読み取られ、シェルがインタラクティブである場合、シェル変数で説明されているように処理された変数PS2の内容を、区切り文字が認識されるまで入力の各行を読み取る前に標準エラーに書き込みます。

まだ与えられていないいくつかの例。

引用はパラメータの展開を妨げます

引用符なし:

a=0
cat <<EOF
$a
EOF

出力:

0

引用付き:

a=0
cat <<'EOF'
$a
EOF

または(醜いが有効):

a=0
cat <<E"O"F
$a
EOF

出力:

$a

ハイフンは先頭のタブを削除します

ハイフンなし:

cat <<EOF
<tab>a
EOF

どこ<tab>リテラルのタブで、かつで挿入することができますCtrl + V <tab>

出力:

<tab>a

ハイフン付き:

cat <<-EOF
<tab>a
<tab>EOF

出力:

a

もちろん、これは存在しているのでcat、周囲のコードと同じようにインデントすることができます。これにより、読みやすく、保守しやすくなります。例えば:

if true; then
    cat <<-EOF
    a
    EOF
fi

残念ながら、これはスペース文字に対しては機能しません。POSIXはtabここではインデントを優先しました。うわぁ。


<<-とについて説明した最後の例<tab>aでは、目的はスクリプト内のコードの通常のインデントを許可し、受信プロセスに提示されるヒアドキュメントのテキストを列0から開始できるようにすることでした。これはあまり一般的に見られる機能ではなく、少しより多くのコンテキストはかなりの引っかき傷を防ぐかもしれません...
デビッドC.ランキン

1
EOFタグの間にあるコンテンツの一部を展開する必要があり、一部を展開しない場合、拡張をどのようにエスケープする必要がありますか?
Jeanmichel Cote 2015

2
...前のバックスラッシュを使用する$
Jeanmichel Cote '23 / 09/23

@JeanmichelCoteより良いオプションは表示されません:-)通常の文字列"$a"'$b'"$c"では、のような引用符の混在を検討することもできますが、AFAIKには類似のものはありません。
Ciro Santilli郝海东冠状病六四事件法轮功

25

猫の代わりにティーを使う

元の質問に対する回答とは異なりますが、とにかくこれを共有したいと思いました。ルート権限を必要とするディレクトリに設定ファイルを作成する必要がありました。

その場合、以下は機能しません。

$ sudo cat <<EOF >/etc/somedir/foo.conf
# my config file
foo=bar
EOF

リダイレクトはsudoコンテキストの外で処理されるためです。

代わりにこれを使用することになりました:

$ sudo tee <<EOF /etc/somedir/foo.conf >/dev/null
# my config file
foo=bar
EOF

あなたのケースではsudo bash -c 'cat << EOF> /etc/somedir/foo.conf#my config file foo = bar EOF'
likewhoa

5

上記の回答に対する少しの拡張。末尾>は、入力をファイルに送り、既存のコンテンツを上書きします。ただし、特に便利な使い方の1つは>>、次のように、ファイルの末尾に新しいコンテンツを追加する二重矢印です。

cat <<EOF >> /etc/fstab
data_server:/var/sharedServer/authority/cert /var/sharedFolder/sometin/authority/cert nfs
data_server:/var/sharedServer/cert   /var/sharedFolder/sometin/vsdc/cert nfs
EOF

これfstabにより、誤って内容を変更する心配をせずに拡張できます。


1

これは必ずしも元の質問に対する回答ではありませんが、自分のテストの結果の一部を共有しています。この:

<<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test

次と同じファイルを生成します:

cat <<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test

したがって、catコマンドを使用する意味がわかりません。


2
どのシェル?Ubuntu 18.04のbash 4.4とOSXのbash 3.2でテストしました。<<testなしで使用すると、どちらも空のファイルを作成しましたcat <<test
wisbucky

これは、zshのLInux Mint 19 Tara
Geoff Langenderfer

0

ここのドキュメントはbashループでも機能することに注意してください。この例は、テーブルの列リストを取得する方法を示しています。

export postgres_db_name='my_db'
export table_name='my_table_name'

# start copy 
while read -r c; do test -z "$c" || echo $table_name.$c , ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
SELECT column_name
FROM information_schema.columns
WHERE 1=1
AND table_schema = 'public'
AND table_name   =:'table_name'  ;
EOF
)
# stop copy , now paste straight into the bash shell ...

output: 
my_table_name.guid ,
my_table_name.id ,
my_table_name.level ,
my_table_name.seq ,

または改行なしでも

while read -r c; do test -z "$c" || echo $table_name.$c , | perl -ne 
's/\n//gm;print' ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
 SELECT column_name
 FROM information_schema.columns
 WHERE 1=1
 AND table_schema = 'public'
 AND table_name   =:'table_name'  ;
 EOF
 )

 # output: daily_issues.guid ,daily_issues.id ,daily_issues.level ,daily_issues.seq ,daily_issues.prio ,daily_issues.weight ,daily_issues.status ,daily_issues.category ,daily_issues.name ,daily_issues.description ,daily_issues.type ,daily_issues.owner
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.