linux - 3>&1隱含4>&3 5>&3等嗎?




bash shell (2)

我希望

echo foo | tee /proc/self/fd/{3..6} 3>&1

/ proc / self / fd / 4之 類的錯誤而失敗 :沒有此類文件或目錄 等,但令我驚訝的是,它輸出

foo
foo
foo
foo
foo

就像 3>&1 一樣,以下所有描述符都被重定向到stdout,但是如果我將 3 更改為其他內容,它將無法正常工作,例如

$ echo foo | tee /proc/self/fd/{3..6} 4>&1
tee: /proc/self/fd/3: No such file or directory
tee: /proc/self/fd/5: No such file or directory
tee: /proc/self/fd/6: No such file or directory
foo
foo
$ echo foo | tee /proc/self/fd/{4..6} 4>&1
tee: /proc/self/fd/5: No such file or directory
tee: /proc/self/fd/6: No such file or directory
foo
foo

有這種行為的解釋嗎?


tee

約翰·庫格曼(John Kugelman)的回答 很好,但是由於問題很複雜,我將進一步介紹:

bash -c 'exec 2> >(exec sed -ue "s/^/StdErr: /");
    exec 1> >(exec sed -ue "s/^/StdOut: /");
    tee /dev/fd/{2..6} <<<foo'
StdErr: foo
StdOut: foo
StdErr: foo
StdErr: foo
StdErr: foo
StdErr: foo

在* STDERR *上顯示輸入( foo )的結果是5倍,在 STDERR 上顯示的結果是1倍。 因此,所有 /dev/fd/{3..6} 都綁定到 /dev/fd/2

strace -o >(grep dev/fd) -e openat tee /dev/fd/{2..6} <<<foo 4>/dev/null
foo
foo
foo
foo
openat(AT_FDCWD, "/dev/fd/2", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
openat(AT_FDCWD, "/dev/fd/3", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 5
openat(AT_FDCWD, "/dev/fd/4", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "/dev/fd/5", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 7
openat(AT_FDCWD, "/dev/fd/6", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 8

所以 teetty 上產生4x輸入而不是5( 2..6 + STDOUT = 6 2..6 / dev / null => 5 參加了 foo ):

  1. 訪問 /dev/fd/2 ,打開 文件描述符3 ...,然後 創建 /dev/fd/3 ,然後
  2. 從上一個操作訪問 /dev/fd/3 ,現在已經存在,打開 文件描述符5 (因為 /dev/fd/4 通過命令行綁定到 /dev/null ,因此 創建 /dev/fd/5 , 然後
  3. 訪問 /dev/fd/4 (通過命令行綁定到 /dev/null )並 創建 /dev/fd/6 ,然後
  4. 訪問 /dev/fd/5 (現在已存在)(綁定到3,綁定到2 ...),然後
  5. 訪問 /dev/fd/6 到現在已經存在,但是綁定到 /dev/fd/4 到現在已綁定到 /dev/null

預期輸出可能是:

tee /dev/fd/{2..6} <<<foo
foo
tee: /dev/fd/3: No such file or directory
tee: /dev/fd/4: No such file or directory
tee: /dev/fd/5: No such file or directory
tee: /dev/fd/6: No such file or directory
foo

使用ouptut在STDERR上執行1次,在STDOUT上執行1次,並設置4條錯誤線。

因此,在開始之前,請不要檢查目標的存在。 但是,這是一個錯誤!

因為即使文件不存在(創建新文件),也可以在 write mode 打開文件。

閱讀 約翰·庫格曼 ( John Kugelman) 的評論所 指出 的使用時間(TOCTOU)競賽條件 有助於理解為什麼進行預檢查是個 錯誤的好主意

因此,如果存在錯誤,我認為 直接通過 tee 引用 /dev/fd/ 就是這裡的錯誤(在命令行上)。 ...不是發 tee ,只是有蟲子的用戶;-)


strace 顯示以下系統調用序列:

$ strace -o strace.log tee /proc/self/fd/{3..6} 3>&1
...
$ cat strace.log
...
openat(AT_FDCWD, "/proc/self/fd/3", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
openat(AT_FDCWD, "/proc/self/fd/4", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 5
openat(AT_FDCWD, "/proc/self/fd/5", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "/proc/self/fd/6", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 7
...

第一行打開 /proc/self/fd/3 並為其分配下一個可用的fd編號 /proc/self/fd/3 是一個特殊路徑。 打開它的效果類似於在fd 3中進行 dup :fd 4指向與tty 3 fd相同的位置。

每個連續的 openat() 調用都會發生相同的情況。 當塵埃落定時,fds 4、5、6和7都是fd 3的副本。

  • 1→tty
  • 3→tty
  • 4→tty
  • 5→tty
  • 6→tty
  • 7→tty

請注意, 3>&1 重定向並不重要。 重要的是,我們要求tee打開 /proc/self/fd/N ,其中 N 已在使用中。 如果我們擺脫 3>&1 並讓tee從 /proc/self/fd/2 開始,我們應該得到相同的結果。 讓我們來看看:

$ echo foo | tee /proc/self/fd/{2..6}
foo
foo
foo
foo
foo
foo

已確認! 結果相同。

我們也可以一遍又一遍地重複相同的fd數。 到達fd 6時,我們得到的結果相同。到最後一個fd時,它已經打開了足夠的描述符以使跳到6成為可能。

$ echo foo | tee /proc/self/fd/{2,2,2,2,6}
foo
foo
foo
foo
foo
foo






proc-filesystem