$ ls > ls.out
現在のディレクトリ内のファイル名のリストに「ls.out」が含まれるのはなぜですか?なぜこれが選ばれたのですか?なぜそうでないのですか?
ls > ../ls.out
$ ls > ls.out
現在のディレクトリ内のファイル名のリストに「ls.out」が含まれるのはなぜですか?なぜこれが選ばれたのですか?なぜそうでないのですか?
ls > ../ls.out
回答:
コマンドを評価するとき、>
リダイレクトが最初に解決されます。そのため、ls
実行されるまでに出力ファイルは既に作成されています。
これは>
、同じコマンド内でリダイレクトを使用して同じファイルを読み書きすると、ファイルが切り捨てられる理由でもあります。コマンドが実行されるまでに、ファイルは既に切り捨てられています。
$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$
これを回避するためのコツ:
<<<"$(ls)" > ls.out
(リダイレクトが解決される前に実行する必要があるコマンドで機能します)
コマンド置換は、外側のコマンドが評価さls
れる前に実行されるため、ls.out
作成される前に実行されます。
$ ls
bar foo
$ <<<"$(ls)" > ls.out
$ cat ls.out
bar
foo
ls | sponge ls.out
(リダイレクトが解決される前に実行する必要があるコマンドで機能します)
sponge
パイプの残りの実行が終了したときにのみファイルに書き込みます。そのため、作成ls
前に実行ls.out
されます(パッケージに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
$
プログラム/スクリプト/実行される前にリダイレクトが解決される理由について、そうすることが必須である特定の理由はわかりませんが、そうする方が良い 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)です。アプリケーションが端末に書き込むために、fd1を開き、ファイルのように書き込みます。
アプリケーションの出力をシェルでリダイレクトすると、fd1が実際にファイルを指すように変更されます。パイプを使用すると、1つのアプリケーションの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
ます。
なぜそこにあるので、多くのジャグリングは?いいえ、私も完全にはわかりません。
もちろん、この動作を変更できます。次のようなものでメモリにバッファリングでき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");
}
}