ファイルの内容を表示する最も簡単な方法は、cat
コマンドを使用することです。
cat file.txt
入力リダイレクトを使用して同じ結果を得ることができます:
cat < file.txt
次に、それらの違いは何ですか?
ユーザーの視点からの違いはありません。これらのコマンドは同じことを行います。
技術的には、どのプログラムがファイルを開くか、つまり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.
cat file
cat
プログラムは、ファイルを開いて読み取り、閉じます。
cat < file
シェルはファイルを開き、内容をcat
の標準入力に接続します。 cat
は、ファイル引数がないことを認識し、標準入力から読み取ります。
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
私はリダイレクトでそれが遅くなるだろうと思いましたが、知覚できる時間差はありません:
$ 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
メモ:
主な違いは、誰がファイルを開くか、シェルまたは猫です。彼らは異なる許可制度で運営している可能性があるため、
Sudo cat /proc/some-protected-file
うまくいくかもしれません
Sudo cat < /proc/some-protected-file
失敗します。この種の権限管理は、スクリプトを簡単にするためにecho
を使用したいだけの場合に対処するのが少し難しい場合があるため、tee
を誤用すると、
echo level 7|Sudo tee /proc/acpi/ibm/fan
これは、権限の問題のため、代わりにリダイレクトを使用しても実際には機能しません。
cat file.txt
を使用すると、アプリケーション(この場合はcat
)が1つの位置パラメータを受け取り、open(2)syscallを実行して、アプリケーション内で権限チェックが行われます。
cat < file.txt
を使用すると、シェルはdup2()
syscallを実行して、標準入力をfile.txt
に対応するファイル記述子(通常は次に使用可能なもの、たとえば3)のコピーにして、そのファイル記述子(たとえば3)を閉じます。アプリケーションはファイルに対してopen(2)を実行せず、ファイルの存在を認識しません。 stdinファイル記述子に厳密に基づいて動作します。権限チェックはシェルに任されています。開いているファイルの説明は、シェルがファイルを開いたときと同じです。
表面的には、cat file.txt
とcat < file.txt
は同じように動作しますが、その背後にある単一の文字の違いにより、多くのことが起こっています。その1つの<
文字は、シェルがfile.txt
を理解する方法、ファイルを開く人、およびシェルとコマンドの間でファイルが受け渡される方法を変更します。もちろん、これらすべての詳細を説明するために、シェルでファイルを開いてコマンドを実行する方法を理解する必要もあります。これが私の答えが達成しようとしていることです。可能な限り簡単に、読者に実際に起こっていることを教育してくださいこれらは一見シンプルなコマンドです。この回答には、 strace コマンドを使用して実際に舞台裏で何が起こっているかの説明をバックアップする例を含む、複数の例があります。
シェルとコマンドが標準のsyscallに基づいてどのように動作するかは内部で機能するため、cat
を他の多くのコマンドの1つとして表示することが重要です。あなたがこの答えを読んでいる初心者である場合は、心を開いて自分自身を設定し、prog file.txt
がprog < file.txt
と常に同じであるとは限らないことに注意してください。異なるコマンドは、2つのフォームがコマンドに適用された場合、まったく異なる動作をする場合があります。これは、権限またはプログラムの記述方法によって異なります。また、判断を一時停止して、さまざまなユーザーの観点からこれを検討してください。カジュアルなシェルユーザーの場合、ニーズはsysadminや開発者とはまったく異なる場合があります。
シェルは、 fork(2) syscallを使用して子プロセスを作成し、 execve(2) syscallを呼び出してコマンドを実行します。これにより、指定された引数と環境変数を使用してコマンドが実行されます。 execve()
内で呼び出されたコマンドは、プロセスを引き継ぎ、置き換えます。たとえば、シェルがcat
を呼び出すと、最初にPID 12345の子プロセスが作成され、execve()
が発生した後、PID 12345はcat
になります。
これにより、cat file.txt
とcat < 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とファイル記述子についても説明します。
誰がファイルを開くのか-cat
またはシェル?彼らはどうやってそれを開くのですか?彼らはそれを開く許可さえ持っていますか?これらは質問できる質問ですが、最初にファイルを開く方法を理解する必要があります。
プロセスがファイルに対してopen()
またはopenat()
を実行すると、それらの関数は開いているファイルに対応する整数をプロセスに提供し、プログラムはread()
を呼び出すことができます。 、seek()
、およびwrite()
呼び出しと、その整数を参照することによる他の無数のシステムコール。もちろん、システム(別名カーネル)は、特定のファイルがどのように開かれ、どのようなアクセス許可があり、どのようなモード(読み取り専用、書き込み専用、読み取り/書き込み)で、現在ファイルのどこにいるかをメモリに保持します-バイト0またはバイト1024-オフセットと呼ばれますこれはopen file descriptionと呼ばれます。
非常に基本的なレベルでは、cat testfile.txt
はcat
がファイルを開き、次に使用可能なファイル記述子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.txt
とcat < 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.txt
とprog < file.txt
はファイルを開くユーザーが異なり、ファイルを開くアクセス許可が異なることです。
< testfile.txt
は、キーボードではなく指定されたファイルからデータが取得されるようにstdinを再書き込みすることをすでに知っています。理論的には、「1つのことを適切に行う」というUnixの哲学に基づいて、stdin(別名ファイル記述子0)から読み取るプログラムは一貫して動作する必要があり、そのためprog1 | prog2
はprog2 file.txt
に類似している必要があります。しかし、prog2
が lseek システムコールで巻き戻したい場合、たとえば、特定のバイトにスキップしたり、 末尾まで巻き戻して、どれだけのデータがあるかを見つけたりする場合 はどうなりますか?
パイプラインは lseek(2) syscallで巻き戻すことができないか、データを mmap(2) でメモリにロードして処理を高速化できないため、特定のプログラムはパイプからのデータの読み取りを許可しません。これは、この質問の Stephane Chazelas の優れた回答でカバーされています。 「cat file | ./binary」と「./binary <file」の違いは何ですか Iそれを読むことを強くお勧めします。
幸いなことに、cat < file.txt
とcat file.txt
は一貫して動作し、cat
はパイプに対してまったく反対ではありませんが、まったく異なるファイル記述子を読み取ることがわかっています。これは、prog file.txt
とprog < file.txt
のどちらに一般的に当てはまりますか?プログラムが実際にパイプで何もしたくない場合は、位置パラメータfile.txt
を指定しなくてもエラーで終了しますが、アプリケーションはstdinでlseek()
を使用して、パイプかどうかを確認できます。 (ただし、 isatty(3) または fstat(2) でのS_ISFIFOモードの検出は、パイプ入力の検出に使用される可能性が高くなります)。この場合、./binary <(grep pattern file.txt)
または./binary < <(grep pattern file.txt)
が機能しない可能性があります。
ファイルの種類は、prog file
とprog < 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
open(2) マニュアルからの引用には、ファイル記述子に対する権限が継承されると記載されています。理論的には、 ファイル記述子の読み取り/書き込み権限を変更する方法があります ですが、ソースコードのレベルで行う必要があります。
command 1>file.txt 2>file.txt
の動作がcommand 1>file.txt 2>&1
と異なるのはなぜですか?