web-dev-qa-db-ja.com

「ls> ls.out」により「ls.out」が名前のリストに含まれるのはなぜですか?

$ ls > ls.outが原因で、現在のディレクトリ内のファイル名のリストに「ls.out」が含まれるのはなぜですか?なぜこれが選ばれたのですか?なぜそうでないのですか?

26
Edward Torvalds

コマンドを評価するとき、>リダイレクトが最初に解決されます。したがって、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が実行されます(spongemoreutils パッケージ):

    $ ls
    bar  foo
    $ ls | sponge ls.out
    $ cat ls.out 
    bar
    foo
    
  • ls * > ls.outls > ls.outの特定のケースに対応)

    ファイル名の展開は、リダイレクトが解決される前に実行されるため、lsはその引数で実行され、ls.outは含まれません。

    $ ls
    bar  foo
    $ ls * > ls.out
    $ cat ls.out 
    bar
    foo
    $
    

プログラム/スクリプト/実行される前にリダイレクトが解決される理由について、mandatoryである特定の理由はわかりませんが、そうすることがbetterである2つの理由を参照してください。

  • sTDINを事前にリダイレクトしないと、STDINがリダイレクトされるまでプログラム/スクリプト/何でも保持されます。

  • 事前にSTDOUTをリダイレクトしない場合は、STDOUTがリダイレクトされるまで、シェルがプログラムの/スクリプトの/何でも出力する必要があります。

したがって、最初のケースでは時間の無駄であり、2番目のケースでは時間とメモリの無駄です。

これは私に起こることです、私はこれらが実際の理由であると主張していません。しかし、すべての場合、選択があれば、上記の理由からとにかく前にリダイレクトすることになります。

36
kos

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

しかし、なぜ?

これについて考えてください-出力はどこになりますか?プログラムにはprintfsprintfputsなどの関数があり、デフォルトではすべてstdoutに移動しますが、ファイルが存在しない場合は出力をファイルに移動できますそもそも?それは水のようなものです。最初に蛇口の下にガラスを置かずにコップ一杯の水を得ることができますか?

11

現在の答えには同意しません。コマンドを実行する前に出力ファイルを開く必要があります。そうしないと、コマンドに出力を書き込む場所がなくなります。

これは、私たちの世界では"すべてがファイルである"であるためです。画面への出力は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
10
Oli

シェルでのリダイレクトおよびパイプ演算子の実装 に関する素敵な記事もあります。 $ 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"); 
    }
}
6
incBrain