web-dev-qa-db-ja.com

「cat <ファイル名」と「catファイル名」の違いは何ですか?

ファイルの内容を表示する最も簡単な方法は、catコマンドを使用することです。

cat file.txt

入力リダイレクトを使用して同じ結果を得ることができます:

cat < file.txt

次に、それらの違いは何ですか?

26
BlueSkies

ユーザーの視点からの違いはありません。これらのコマンドは同じことを行います。

技術的には、どのプログラムがファイルを開くか、つまりcatプログラムまたはそれを実行するシェルに違いがあります。リダイレクトは、シェルがコマンドを実行する前にセットアップします。

(つまり、一部のotherコマンド-つまり、notの質問に示されているコマンドでは、違いがある可能性があります。特に、file.txtにアクセスできない場合ただし、rootユーザーはできますが、Sudo cat file.txtは機能しますが、Sudo cat < file.txtは機能しません。)

あなたの場合に便利などちらかを使用できます。

ほとんどの場合、同じ結果を得るには多くの方法があります。

catは引数からファイルを受け入れます。引数がない場合はstdinを受け入れます。

man catを参照してください:

SYNOPSIS
       cat [OPTION]... [FILE]...

DESCRIPTION
       Concatenate FILE(s) to standard output.

       With no FILE, or when FILE is -, read standard input.
14
Pilot6
cat file

catプログラムは、ファイルを開いて読み取り、閉じます。

cat < file

シェルはファイルを開き、内容をcatの標準入力に接続します。 catは、ファイル引数がないことを認識し、標準入力から読み取ります。

59
glenn jackman

1つの大きな違い

1つの大きな違いは、*?、または[グロブ文字(ワイルドカード)など、シェルは複数のファイル名に展開できます。シェルが単一のファイル名として扱うのではなく、2つ以上のアイテムに展開するものは、リダイレクトのために開くことができません。

リダイレクトなし(つまり、<)、シェルは複数のファイル名をcatに渡し、ファイルの内容を次々に出力します。たとえば、これは動作します:

$ ls hello?.py
hello1.py  hello2.py

$ cat hello?.py

# Output for two files 'hello1.py' and 'hello2.py' appear on your screen

しかし、リダイレクト(<)エラーメッセージが表示されます。

$ ls < hello?.py
bash: hello?.py: ambiguous redirect

$ cat < hello?.py
bash: hello?.py: ambiguous redirect

1つの小さな違い

私はリダイレクトでそれが遅くなるだろうと思いましたが、知覚できる時間差はありません:

$ time for f in * ; do cat "$f" > /dev/null ; done

real    0m3.399s
user    0m0.130s
sys     0m1.940s

$ time for f in * ; do cat < "$f" > /dev/null ; done

real    0m3.430s
user    0m0.100s
sys     0m2.043s

メモ:

  • このテストでは、違いは1秒の約1/1000(1000分の1)です。他のテストでは、それはまだ気付かれない1/100秒でした。
  • テストを数回交互に実行して、データがRAMにできるだけ多くキャッシュされ、より一貫した比較時間が返されるようにします。別のオプションは、各テストの前にすべてのキャッシュを削除することです。
14

主な違いは、誰がファイルを開くか、シェルまたは猫です。彼らは異なる許可制度で運営している可能性があるため、

Sudo cat /proc/some-protected-file

うまくいくかもしれません

Sudo cat < /proc/some-protected-file

失敗します。この種の権限管理は、スクリプトを簡単にするためにechoを使用したいだけの場合に対処するのが少し難しい場合があるため、teeを誤用すると、

echo level 7|Sudo tee /proc/acpi/ibm/fan

これは、権限の問題のため、代わりにリダイレクトを使用しても実際には機能しません。

10
user1018475

TL;回答のDRバージョン:

  • cat file.txtを使用すると、アプリケーション(この場合はcat)が1つの位置パラメータを受け取り、open(2)syscallを実行して、アプリケーション内で権限チェックが行われます。

  • cat < file.txtを使用すると、シェルはdup2() syscallを実行して、標準入力をfile.txtに対応するファイル記述子(通常は次に使用可能なもの、たとえば3)のコピーにして、そのファイル記述子(たとえば3)を閉じます。アプリケーションはファイルに対してopen(2)を実行せず、ファイルの存在を認識しません。 stdinファイル記述子に厳密に基づいて動作します。権限チェックはシェルに任されています。開いているファイルの説明は、シェルがファイルを開いたときと同じです。

前書き

表面的には、cat file.txtcat < file.txtは同じように動作しますが、その背後にある単一の文字の違いにより、多くのことが起こっています。その1つの<文字は、シェルがfile.txtを理解する方法、ファイルを開く人、およびシェルとコマンドの間でファイルが受け渡される方法を変更します。もちろん、これらすべての詳細を説明するために、シェルでファイルを開いてコマンドを実行する方法を理解する必要もあります。これが私の答えが達成しようとしていることです。可能な限り簡単に、読者に実際に起こっていることを教育してくださいこれらは一見シンプルなコマンドです。この回答には、 strace コマンドを使用して実際に舞台裏で何が起こっているかの説明をバックアップする例を含む、複数の例があります。

シェルとコマンドが標準のsyscallに基づいてどのように動作するかは内部で機能するため、catを他の多くのコマンドの1つとして表示することが重要です。あなたがこの答えを読んでいる初心者である場合は、心を開いて自分自身を設定し、prog file.txtprog < file.txtと常に同じであるとは限らないことに注意してください。異なるコマンドは、2つのフォームがコマンドに適用された場合、まったく異なる動作をする場合があります。これは、権限またはプログラムの記述方法によって異なります。また、判断を一時停止して、さまざまなユーザーの観点からこれを検討してください。カジュアルなシェルユーザーの場合、ニーズはsysadminや開発者とはまったく異なる場合があります。

execve()Syscallと実行可能ファイルが見る位置パラメータ

シェルは、 fork(2) syscallを使用して子プロセスを作成し、 execve(2) syscallを呼び出してコマンドを実行します。これにより、指定された引数と環境変数を使用してコマンドが実行されます。 execve()内で呼び出されたコマンドは、プロセスを引き継ぎ、置き換えます。たとえば、シェルがcatを呼び出すと、最初にPID 12345の子プロセスが作成され、execve()が発生した後、PID 12345はcatになります。

これにより、cat file.txtcat < file.txtの違いがわかります。最初のケースでは、cat file.txtは1つの定位置パラメーターで呼び出されるコマンドであり、シェルはexecve()を適切にまとめます。

$ strace -e execve cat testfile.txt
execve("/bin/cat", ["cat", "testfile.txt"], 0x7ffcc6ee95f8 /* 50 vars */) = 0
hello, I am testfile.txt
+++ exited with 0 +++

2番目のケースでは、<部分がシェル演算子であり、< testfile.txtがシェルにtestfile.txtを開き、stdinファイル記述子0をtestfile.txtに対応するファイル記述子のコピーにするように指示します。これは、< testfile.txtが位置引数としてコマンド自体に渡されないことを意味します。

$ strace -e execve cat < testfile.txt
execve("/bin/cat", ["cat"], 0x7ffc6adb5490 /* 50 vars */) = 0
hello, I am testfile.txt
+++ exited with 0 +++
$ 

プログラムが適切に機能するために定位置パラメーターを必要とする場合、これは重要になる可能性があります。この場合、catは、ファイルに対応する定位置パラメーターが指定されていない場合、デフォルトでstdinからの入力を受け入れます。また、次のトピックであるstdinとファイル記述子についても説明します。

STDINおよびファイル記述子

誰がファイルを開くのか-catまたはシェル?彼らはどうやってそれを開くのですか?彼らはそれを開く許可さえ持っていますか?これらは質問できる質問ですが、最初にファイルを開く方法を理解する必要があります。

プロセスがファイルに対してopen()またはopenat()を実行すると、それらの関数は開いているファイルに対応する整数をプロセスに提供し、プログラムはread()を呼び出すことができます。 、seek()、およびwrite()呼び出しと、その整数を参照することによる他の無数のシステムコール。もちろん、システム(別名カーネル)は、特定のファイルがどのように開かれ、どのようなアクセス許可があり、どのようなモード(読み取り専用、書き込み専用、読み取り/書き込み)で、現在ファイルのどこにいるかをメモリに保持します-バイト0またはバイト1024-オフセットと呼ばれますこれはopen file descriptionと呼ばれます。

非常に基本的なレベルでは、cat testfile.txtcatがファイルを開き、次に使用可能なファイル記述子3によって参照されます( read(2) の3に注意)。

$ strace -e read -f cat testfile.txt > /dev/null
...
read(3, "hello, I am testfile.txt and thi"..., 131072) = 79
read(3, "", 131072)                     = 0
+++ exited with 0 +++

対照的に、cat < testfile.txtはファイル記述子0(別名stdin)を使用します。

$ strace -e read -f cat < testfile.txt > /dev/null
...
read(0, "hello, I am testfile.txt and thi"..., 131072) = 79
read(0, "", 131072)                     = 0
+++ exited with 0 +++

以前にシェルがfork()を介してコマンドを実行し、次にexec()タイプのプロセスを実行することを以前に学習したことを覚えていますか?さて、howファイルが開いていることが判明し、fork()/exec()パターンで作成された子プロセスに虫歯ができるようになりました。引用するには open(2)manual

(dup(2)などを使用して)ファイル記述子が複製されると、複製は元のファイル記述子と同じ開いているファイルの説明を参照します。その結果、2つのファイル記述子はファイルオフセットとファイルステータスフラグを共有します。このような共有は、プロセス間でも発生する可能性があります。fork(2)で作成された子プロセスは、親のファイル記述子の重複、およびそれらの重複は同じ開いているファイルの説明を参照します

これは、cat file.txtcat < file.txtの違いは何ですか?実際にたくさん。 cat file.txtでは、catがファイルを開きます。つまり、ファイルを開く方法を制御します。 2番目のケースでは、Shellはfile.txtを開き、子プロセス、複合コマンド、およびパイプラインの場合、その開き方は変更されません。ファイル内の現在の場所も同じままです。

例としてこのファイルを使用してみましょう:

$ cat testfile.txt 
hello, I am testfile.txt and this is first line
line two
line three

last line

以下の例を見てください。なぜ最初の行でlineが変更されなかったのですか?

$ { head -n1; sed 's/line/potato/'; }  <  testfile.txt 2>/dev/null
hello, I am testfile.txt and this is first line
potato two
potato three

last potato

答えは上記の open(2) マニュアルの引用にあります。シェルによって開かれたファイルは複合コマンドのstdinに複製され、実行される各コマンド/プロセスは開いているファイルの説明のオフセットを共有します。 headは単にファイルを1行先に巻き戻し、sedは残りを処理しました。より具体的には、dup2()/fork()/execve() syscallsの2つのシーケンスが表示され、それぞれのケースで同じを参照するファイル記述子のコピーが取得されます開いているtestfile.txtのファイルの説明。混乱していますか?少しクレイジーな例を見てみましょう:

$ { head -n1; dd of=/dev/null bs=1 count=5; cat; }  <  testfile.txt 2>/dev/null
hello, I am testfile.txt and this is first line
two
line three

last line

ここでは、最初の行を印刷してから、開いているファイルの説明を5バイト先に巻き戻し(Wordのlineを削除した)、残りの部分を印刷しました。そして、どうやってそれを成し遂げたのでしょうか? testfile.txtの開いているファイルの説明は同じままで、ファイルのオフセットは共有されます。

では、上記のようなクレイジーな複合コマンドを作成する以外に、なぜこれが理解に役立つのでしょうか。開発者として、そのような振る舞いを利用したり、注意したりすることができます。 catの代わりに、ファイルとしてまたはstdinから渡される構成を必要とするCプログラムを作成し、それをmyprog myconfig.jsonのように実行するとします。代わりに{ head -n1; myprog;} < myconfig.jsonを実行するとどうなりますか?せいぜいあなたのプログラムは不完全な設定データを取得し、最悪の場合、プログラムを壊します。これを、子プロセスを生成し、親が子プロセスが処理するデータに巻き戻す利点としても使用できます。

権限と特権

今度は、他のユーザーに対する読み取りまたは書き込み権限のないファイルの例から始めましょう。

$ Sudo -u potato cat < testfile.txt
hello, I am testfile.txt and this is first line
line two
line three

last line
$ Sudo -u potato cat testfile.txt
cat: testfile.txt: Permission denied

ここで何が起こりましたか?最初の例ではpotatoユーザーとしてファイルを読み取ることができますが、2番目の例では読み取れないのはなぜですか?これは、前述の open(2) のマニュアルページと同じ引用に戻ります。 < file.txtを使用すると、シェルがファイルを開くため、 権限チェックは、シェルがopen/openat() を実行するときに行われます。そのときのシェルは、ファイルの読み取り権限を持つファイル所有者の権限で実行されます。オープンファイルの説明がdup2呼び出し全体で継承されるため、シェルはオープンファイル記述子のコピーをSudoに渡し、ファイル記述子のコピーをcatに渡し、catは他のことを何も知らないため、ファイルの内容を喜んで読み取ります。最後のコマンドでは、potatoユーザーの下のcatがファイルに対してopen()を実行し、もちろん、そのユーザーにはファイルを読み取る権限がありません。

より実用的かつ一般的に、これは、ユーザーがこのような何かが機能しない理由について困惑する理由です(特権コマンドを実行して、開くことができないファイルに書き込みます)。

$ Sudo echo 100 > /sys/class/drm/*/intel_backlight/brightness
bash: /sys/class/drm/card0-eDP-1/intel_backlight/brightness: Permission denied

しかし、次のようなものが機能します(特権を必要とするファイルに書き込む特権コマンドを使用):

$ echo 100 |Sudo tee /sys/class/drm/*/intel_backlight/brightness
[Sudo] password for administrator: 
100

前に示した状況(privileged_prog < file.txtは失敗するが、privileged_prog file.txtは機能する)とは逆の状況の理論的な例は、SUIDプログラムの場合です。 passwdなどの SUIDプログラム では、実行可能所有者の権限でアクションを実行できます。これが、ファイルがrootユーザーによって所有されている場合でも、passwdコマンドを使用してパスワードを変更し、その変更を / etc/shadow に書き込むことができる理由です。

例と楽しみのために、私は実際にCでクイックデモcatのようなアプリケーションをSUIDビットを設定して記述します( ソースコード ここ)。この答えのこの部分を無視します。補足:OSは#!を使用して解釈された実行可能ファイルのSUIDビットを無視するため、Pythonこの同じバージョンのバージョンは失敗します。

プログラムとtestfile.txtの権限を確認してみましょう。

$ ls -l ./privileged
-rwsr-xr-x 1 administrator administrator 8672 Nov 29 16:39 ./privileged
$ ls -l testfile.txt
-rw-r----- 1 administrator administrator 79 Nov 29 12:34 testfile.txt

このファイルを読み取ることができるのは、ファイルの所有者とadministratorグループに属する人だけです。次に、potatoユーザーとしてログインし、ファイルを読み取ってみましょう。

$ su potato
Password: 
potato@my-PC:/home/administrator$ cat ./testfile.txt
cat: ./testfile.txt: Permission denied
potato@my-PC:/home/administrator$ cat  < ./testfile.txt
bash: ./testfile.txt: Permission denied

OKに見えます。ポテトユーザー権限を持つシェルもcatも、読み取りが許可されていないファイルを読み取ることはできません。誰がエラーを報告したかにも注意してください-cat vs bash。 SUIDプログラムをテストしてみましょう。

potato@my-PC:/home/administrator$ ./privileged testfile.txt
hello, I am testfile.txt and this is first line
line two
line three

last line
potato@my-PC:/home/administrator$ ./privileged < testfile.txt
bash: testfile.txt: Permission denied

意図したとおりに機能します!繰り返しになりますが、この小さなデモのポイントは、prog file.txtprog < file.txtはファイルを開くユーザーが異なり、ファイルを開くアクセス許可が異なることです。

プログラムReact to STDIN

< testfile.txtは、キーボードではなく指定されたファイルからデータが取得されるようにstdinを再書き込みすることをすでに知っています。理論的には、「1つのことを適切に行う」というUnixの哲学に基づいて、stdin(別名ファイル記述子0)から読み取るプログラムは一貫して動作する必要があり、そのためprog1 | prog2prog2 file.txtに類似している必要があります。しかし、prog2lseek システムコールで巻き戻したい場合、たとえば、特定のバイトにスキップしたり、 末尾まで巻き戻して、どれだけのデータがあるかを見つけたりする場合 はどうなりますか?

パイプラインは lseek(2) syscallで巻き戻すことができないか、データを mmap(2) でメモリにロードして処理を高速化できないため、特定のプログラムはパイプからのデータの読み取りを許可しません。これは、この質問の Stephane Chazelas の優れた回答でカバーされています。 「cat file | ./binary」と「./binary <file」の違いは何ですか Iそれを読むことを強くお勧めします。

幸いなことに、cat < file.txtcat file.txtは一貫して動作し、catはパイプに対してまったく反対ではありませんが、まったく異なるファイル記述子を読み取ることがわかっています。これは、prog file.txtprog < file.txtのどちらに一般的に当てはまりますか?プログラムが実際にパイプで何もしたくない場合は、位置パラメータfile.txtを指定しなくてもエラーで終了しますが、アプリケーションはstdinでlseek()を使用して、パイプかどうかを確認できます。 (ただし、 isatty(3) または fstat(2) でのS_ISFIFOモードの検出は、パイプ入力の検出に使用される可能性が高くなります)。この場合、./binary <(grep pattern file.txt)または./binary < <(grep pattern file.txt)が機能しない可能性があります。

ファイルタイプの影響

ファイルの種類は、prog fileprog < fileの動作に影響する場合があります。これは、プログラムのユーザーとして、意識していない場合でも、syscallを選択していることをある程度示唆しています。たとえば、Unixドメインソケットがあり、それをリッスンするためにncサーバーを実行するとします。

$ nc -U -l /tmp/mysocket.sock   < testfile.txt 

この場合、/tmp/mysocket.sockは異なるシステムコールを介して開かれます:

socket(AF_UNIX, SOCK_STREAM, 0)         = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_UNIX, Sun_path="/tmp/mysocket.sock"}, 20) = 0

次に、別の端末でそのソケットからデータを読み取ってみましょう。

$ cat /tmp/mysocket.sock
cat: /tmp/mysocket.sock: No such device or address
$ cat <  /tmp/mysocket.sock
bash: /tmp/mysocket.sock: No such device or address

Shellとcatの両方が、まったく異なるsyscall(socket(2)とconnect(2)のペア)を必要とするものに対してopen(2) syscallを実行しています。これでもうまくいきません:

$ nc -U  < /tmp/mysocket.sock
bash: /tmp/mysocket.sock: No such device or address

しかし、ファイルタイプと適切なsyscallを呼び出す方法を意識している場合は、目的の動作を得ることができます。

$ nc -U /tmp/mysocket.sock
hello, I am testfile.txt and this is first line
line two
line three

last line

注意事項とその他の推奨される読み物:

6