子の代わりに親のファイル記述子をコピーします。

子の代わりに親のファイル記述子をコピーします。

リダイレクトの使用方法を学んでいます。一般的な作業は次のとおりです。

    command > file 2>&1

APUE 3.10と3.12を参照すると、コアシステムコールの順序は次のとおりです。

    open(file) == 3
    dup2(3,1)
    dup2(1,2)

私のアイデアをテストするためにシェルスクリプトを生成し、straceコマンドを使用して実行しました。私のtest.shは次のようになります。

    #!/bin/sh
    echo 'hello'
    whatfuckis # For get a stderr

次に、straceコマンドを使用してシステムコールとシグナルを追跡します。

    strace -y -o trace.log ./test.sh > tmp.txt 2>&1

tmp.txtは期待どおりにエラーメッセージを受け取ります。 Trace.logには、読み取り、書き込み、閉じる、fcntl..などの多くのシステムコールが表示され、ファイル記述子2(stderr)がtmp.txtを指す結果も表示されます。

問題は、dup2などのファイル記述子のコピーに関する特定の操作が表示されないことです。これは奇妙です。ファイル記述子の重複を追跡する方法は? Trace.logの主な情報は次のとおりです。

read(10</home/madhouse/Applications/Lab/test.sh>, "#!/bin/sh\n\necho 'hello'\nwhatfuck"..., 8192) = 54
write(1</home/madhouse/Applications/Lab/tmp.txt>, "hello\n", 6) = 6
stat("/home/MATLAB/R2016b/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/home/madhouse/Qt5.14.2/5.14.2/gcc_64//whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/home/madhouse/Qt5.14.2/Tools/QtCreator/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/local/lib/nodejs/node-v12.22.5-linux-x64/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/home/madhouse/.local/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/local/sbin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/sbin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/games/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/local/games/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/snap/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
write(2</home/madhouse/Applications/Lab/tmp.txt>, "./test.sh: 4: ", 14) = 14
write(2</home/madhouse/Applications/Lab/tmp.txt>, "whatfuckis: not found", 21) = 21
write(2</home/madhouse/Applications/Lab/tmp.txt>, "\n", 1) = 1
read(10</home/madhouse/Applications/Lab/test.sh>, "", 8192) = 0
exit_group(127)                         = ?
+++ exited with 127 +++

アビロのログ

167936 execve("/usr/bin/sh", ["sh", "-c", "./test.sh > tmp.txt 2>&1"], 0x7ffc000ac898 /* 76 vars */) = 0
167936 dup2(3</home/madhouse/Applications/Lab/tmp.txt>, 1) = 1</home/madhouse/Applications/Lab/tmp.txt>
167936 close(3</home/madhouse/Applications/Lab/tmp.txt>) = 0
167936 dup2(1</home/madhouse/Applications/Lab/tmp.txt>, 2) = 2</home/madhouse/Applications/Lab/tmp.txt>
167936 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f67e121d850) = 167937
167937 execve("./test.sh", ["./test.sh"], 0x55855ac8c2d8 /* 76 vars */) = 0
167937 write(1</home/madhouse/Applications/Lab/tmp.txt>, "hello\n", 6) = 6
167937 write(2</home/madhouse/Applications/Lab/tmp.txt>, "./test.sh: 4: ", 14) = 14
167937 write(2</home/madhouse/Applications/Lab/tmp.txt>, "whatfuckis: not found", 21) = 21
167937 write(2</home/madhouse/Applications/Lab/tmp.txt>, "\n", 1) = 1
167936 dup2(10</dev/pts/2>, 1</home/madhouse/Applications/Lab/tmp.txt>) = 1</dev/pts/2>
167936 dup2(11</dev/pts/2>, 2</home/madhouse/Applications/Lab/tmp.txt>) = 2</dev/pts/2>

答え1

2つの質問があります。

  1. コマンドはリダイレクトを実行しているため、strace対話型シェルでリダイレクトが実行され、関連するstraceシステムコールは表示されません。
  2. システムdup2コールは、コマンドが実行される前に子プロセス内で実行されます。デフォルトでは、straceコマンドのサブアイテムは従わないため、サブアイテム自体のトレースは表示されません。タグがサブアイテムにも従うようにするには、-fマークアップにタグを追加する必要があります。strace

最も重要なことは、予想される検査を実行するには、次のことを実行する必要があることです。

strace -yf -o trace.log sh -c './test.sh > tmp.txt 2>&1'

これにより、予想される順序が表示されます。

$ grep -E 'dup2\(|clone\(|execve\(|(open|write|close)\(.*tmp.txt' trace.log 
31769 execve("/usr/bin/sh", ["sh", "-c", "./test.sh > tmp.txt 2>&1"], [/* 124 vars */]) = 0
31769 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ffff7fd19d0) = 31770
31770 open("tmp.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3</tmp/tmp.txt>
31770 dup2(3</tmp/tmp.txt>, 1</dev/pts/445>) = 1</tmp/tmp.txt>
31770 close(3</tmp/tmp.txt>)            = 0
31770 dup2(1</tmp/tmp.txt>, 2</dev/pts/445>) = 2</tmp/tmp.txt>
31770 execve("./test.sh", ["./test.sh"], [/* 123 vars */]) = 0
31770 dup2(3</tmp/test.sh>, 255)        = 255</tmp/test.sh>
31770 write(1</tmp/tmp.txt>, "hello\n", 6) = 6
31770 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ffff7fd19d0) = 31771
31771 write(2</tmp/tmp.txt>, "./test.sh: line 3: whatfuckis: c"..., 49) = 49

システムコールは、実際の実行前にopenコマンドのサブコマンドpiddup2内で実行されます。31770sh -c...test.sh

子の代わりに親のファイル記述子をコピーします。

(シェル実装に応じて)実行が可能ですclone。この場合、親はソースを保存する必要があります。標準出力そして標準エラーエラー率ファイル記述子を削除し、子プロセスが終了した後に復元します。

$ grep -E 'dup2\(|clone\(|execve\(|(open|write|close|openat)\(.*tmp.txt|F_DUPFD|exit_group' trace.log
1094  execve("/usr/bin/sh", ["sh", "-c", "./test.sh > tmp.txt 2>&1"], 0x7fffd0324118 /* 20 vars */) = 0
1094  openat(AT_FDCWD, "tmp.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3</tmp/tmp.txt>
1094  fcntl(1</dev/pts/0>, F_DUPFD, 10) = 10</dev/pts/0>
1094  dup2(3</tmp/tmp.txt>, 1)          = 1</tmp/tmp.txt>
1094  close(3</tmp/tmp.txt>)            = 0
1094  fcntl(2</dev/pts/0>, F_DUPFD, 10) = 11</dev/pts/0>
1094  dup2(1</tmp/tmp.txt>, 2)          = 2</tmp/tmp.txt>
1094  clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f844aa71690) = 1095
1095  execve("./test.sh", ["./test.sh"], 0x7f844aa9dc08 /* 20 vars */) = 0
1095  fcntl(3</tmp/test.sh>, F_DUPFD, 10) = 10</tmp/test.sh>
1095  write(1</tmp/tmp.txt>, "hello\n", 6) = 6
1095  write(2</tmp/tmp.txt>, "./test.sh: 3: ", 14) = 14
1095  write(2</tmp/tmp.txt>, "whatfuckis: not found", 21) = 21
1095  write(2</tmp/tmp.txt>, "\n", 1)   = 1
1095  exit_group(127)                   = ?
1094  dup2(10</dev/pts/0>, 1</tmp/tmp.txt>) = 1</dev/pts/0>
1094  dup2(11</dev/pts/0>, 2</tmp/tmp.txt>) = 2</dev/pts/0>
1094  exit_group(127) 

dup2各実行を開始する前に、1094親プロセス(pid)がファイル記述子1と2をファイル記述子10と11にそれぞれコピーする方法(fcntlシステムコールとF_DUPFDコマンドを使用)を確認してください。

その後、子プロセス(pid 1095)が完了すると、親プロセスはファイル記述子10と11(元のファイル記述子)を復元します。標準出力そして標準エラーエラー率) ファイル記述子 1 と 2 に戻ります。

関連情報