$ ls > ls.out
が原因で、現在のディレクトリ内のファイル名のリストに「ls.out」が含まれるのはなぜですか?なぜこれが選ばれたのですか?なぜそうでないのですか?
コマンドを評価するとき、>
リダイレクトが最初に解決されます。したがって、ls
が実行されるまでに、出力ファイルは既に作成されています。
これは、同じコマンド内で>
リダイレクションを使用して同じファイルを読み書きすると、ファイルが切り捨てられる理由でもあります。コマンドが実行されるまでに、ファイルは既に切り捨てられています。
$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$
これを回避するためのコツ:
<<<"$(ls)" > ls.out
(リダイレクトを解決する前に実行する必要のあるコマンドで機能します)
コマンド置換は外部コマンドが評価される前に実行されるため、ls.out
が作成される前にls
が実行されます。
$ ls
bar foo
$ <<<"$(ls)" > ls.out
$ cat ls.out
bar
foo
ls | sponge ls.out
(リダイレクトが解決される前に実行する必要があるコマンドで機能します)
sponge
は、残りのパイプの実行が終了したときにのみファイルに書き込みます。したがって、ls.out
が作成される前にls
が実行されます(sponge
はmoreutils
パッケージ):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
ls * > ls.out
(ls > ls.out
の特定のケースに対応)
ファイル名の展開は、リダイレクトが解決される前に実行されるため、ls
はその引数で実行され、ls.out
は含まれません。
$ ls
bar foo
$ ls * > ls.out
$ cat ls.out
bar
foo
$
プログラム/スクリプト/実行される前にリダイレクトが解決される理由について、mandatoryである特定の理由はわかりませんが、そうすることがbetterである2つの理由を参照してください。
sTDINを事前にリダイレクトしないと、STDINがリダイレクトされるまでプログラム/スクリプト/何でも保持されます。
事前にSTDOUTをリダイレクトしない場合は、STDOUTがリダイレクトされるまで、シェルがプログラムの/スクリプトの/何でも出力する必要があります。
したがって、最初のケースでは時間の無駄であり、2番目のケースでは時間とメモリの無駄です。
これは私に起こることです、私はこれらが実際の理由であると主張していません。しかし、すべての場合、選択があれば、上記の理由からとにかく前にリダイレクトすることになります。
man bash
から:
リダイレクション
コマンドを実行する前に、シェルによって解釈される特別な表記法を使用して、コマンドの入力と出力をリダイレクトできます。リダイレクションを使用すると、コマンドのファイルハンドルを複製、開く、閉じる、別のファイルを参照することができ、コマンドが読み書きするファイルを変更できます。
最初の文は、コマンドが実行される直前に、リダイレクトによってstdin
以外の場所に出力することを示唆しています。したがって、ファイルにリダイレクトするには、最初にシェル自体でファイルを作成する必要があります。
ファイルを持たないようにするには、最初に出力を名前付きパイプにリダイレクトし、次にファイルにリダイレクトすることをお勧めします。 &
を使用して端末の制御をユーザーに返すことに注意してください
DIR:/xieerqi
skolodya@ubuntu:$ mkfifo /tmp/namedPipe.fifo
DIR:/xieerqi
skolodya@ubuntu:$ ls > /tmp/namedPipe.fifo &
[1] 14167
DIR:/xieerqi
skolodya@ubuntu:$ cat /tmp/namedPipe.fifo > ls.out
しかし、なぜ?
これについて考えてください-出力はどこになりますか?プログラムにはprintf
、sprintf
、puts
などの関数があり、デフォルトではすべてstdout
に移動しますが、ファイルが存在しない場合は出力をファイルに移動できますそもそも?それは水のようなものです。最初に蛇口の下にガラスを置かずにコップ一杯の水を得ることができますか?
現在の答えには同意しません。コマンドを実行する前に出力ファイルを開く必要があります。そうしないと、コマンドに出力を書き込む場所がなくなります。
これは、私たちの世界では"すべてがファイルである"であるためです。画面への出力はSDOUT(別名ファイル記述子1)です。アプリケーションが端末に書き込むために、opensfd1をファイルのように書き込みます。
シェルでアプリケーションの出力をリダイレクトすると、fd1が実際にファイルを指すように変更されます。パイプを使用すると、あるアプリケーションのSTDOUTを変更して、別のアプリケーションのSTDIN(fd0)にします。
しかし、それはすべていいことですが、これがstrace
でどのように機能するかを簡単に見ることができます。かなり重いものですが、この例は非常に短いものです。
strace sh -c "ls > ls.out" 2> strace.out
strace.out
内で、次のハイライトを確認できます。
open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
これにより、ls.out
としてfd3
が開きます。書き込みのみ。存在する場合は切り捨て(上書き)、それ以外の場合は作成します。
fcntl(1, F_DUPFD, 10) = 10
close(1) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
これは少しジャグリングです。 STDOUT(fd1)をfd10に分流して閉じます。これは、このコマンドで実際のSTDOUTに何も出力していないためです。書き込みハンドルをls.out
に複製し、元のハンドルを閉じて終了します。
stat("/opt/wine-staging/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/home/oli/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/bin/ls", {st_mode=S_IFREG|0755, st_size=110080, ...}) = 0
これは、実行可能ファイルの検索です。おそらく長い道のりを持たないためのレッスン;)
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f0961324a10) = 31933
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 31933
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=31933, si_status=0, si_utime=0, si_stime=0} ---
rt_sigreturn() = 31933
dup2(10, 1) = 1
close(10) = 0
次に、コマンドが実行され、親が待機します。この操作中、STDOUTはls.out
の開いているファイルハンドルに実際にマップされます。子がSIGCHLD
を発行すると、これは親プロセスに終了したことと再開できることを伝えます。最後にもう少しジャグリングしてls.out
を閉じます。
なぜsoジャグリングが多いのですか?いいえ、私も完全にはわかりません。
もちろん、この動作を変更できます。 sponge
のようなものでメモリにバッファすることができ、それは次のコマンドからは見えなくなります。私たちはまだファイル記述子に影響を及ぼしていますが、ファイルシステムからは見えません。
ls | sponge ls.out
シェルでのリダイレクトおよびパイプ演算子の実装 に関する素敵な記事もあります。 $ ls > ls.out
が次のようになるようにリダイレクトを実装する方法を示しています。
main(){
close(1); // Release fd no - 1
open("ls.out", "w"); // Open a file with fd no = 1
// Child process
if (fork() == 0) {
exec("ls");
}
}