Lsof

邊緣情況 - 在 perl 中檢測 STDIN 上的輸入

  • August 17, 2021

我不知道如何問這個問題,我什至不確定這是問這個問題的地方。它似乎相當複雜,我對正在發生的事情沒有完全了解。坦率地說,這就是我發帖的原因 - 以獲得一些幫助來解決這個問題。我的最終目標是學習,而不是解決我的整體問題。我想了解我何時會遇到我將要描述的情況以及為什麼會發生。

我有一個我一直在開發的 perl 模組。它所做的一件事是它檢測是否有標準輸入(無論是通過管道還是通過重定向(即<))。

為了擷取重定向,我對各種情況採用了一些不同的檢查。其中之一是在輸出中尋找0r文件描述符。lsof它工作得很好,我在很多腳本中使用我的模組都沒有問題,但是我有 1 個案例,我的腳本認為它在 STDIN 上得到輸入,但它不是 - 它與我得到的內容有關lsof 輸出。以下是我將案件範圍縮小到的條件,但這些並不是全部要求 - 我遺漏了一些東西。無論如何,這些條件似乎是必需的,但請對我的直覺持保留態度,因為我真的不知道如何在玩具範例中實現它 - 我已經嘗試過 - 這就是為什麼我知道我錯過了某物:

  1. 當我通過反引號從 perl 腳本中執行 perl 腳本時,(內部腳本認為它已在 STDIN 上故意輸入輸入,而實際上它沒有 - 儘管我應該指出我不知道它是否是實際打開該句柄的父或子)
  2. 輸入文件被提供給駐留在子目錄中的內部腳本呼叫

0r具有lsof 報告的文件描述符的文件是:

/Library/Perl/5.18/AppendToPath

在其他條件下,此文件不會出現在 lsof 輸出中。如果我eof(STDIN)在通話前後都這樣做lsof,結果每次都是 1。 -t STDIN未定義。 fileno(STDIN)0

我在這裡閱讀了有關此文件的資訊,如果我發現它,它有:

>cat /Library/Perl/5.18/AppendToPath
/System/Library/Perl/Extras/5.18

看來這是一個特定於 macOS perl 的文件,旨在附加到@INCperl 路徑,但我不知道其他作業系統是否提供類似的機制。

我想更多地了解該文件何時存在/打開以及何時關閉。我可以關閉它嗎?似乎解釋器可能已經讀入了文件內容 - 那麼為什麼它作為打開的文件句柄在我的腳本中徘徊?為什麼它在標準輸入上?當我實際重定向自己的文件時,在這種情況下會發生什麼?在我不知道的某些情況下,子程序是否以某種方式從父程序繼承了它?

更新:我想出了在子腳本的腳本執行期間使 AppendToPath 文件句柄在 STDIN 上打開所需的第三個(可能是最終的)要求。事實證明,我在父腳本的頂部有一行程式碼(當我對檢測 STDIN 輸入的了解甚至比現在還少時,可能添加它來嘗試解決類似的問題)正在關閉 STDIN。我註釋掉了關閉並且一切都開始工作而無需排除那個奇怪的文件(即該文件:/Library/Perl/5.18/AppendToPath不再在 lsof 中的 STDIN 上顯示為打開)。這是我註釋掉的程式碼:

close(STDIN) if(defined(fileno(STDIN)) && fileno(STDIN) ne '' &&
               fileno(STDIN) > -1);

它上面有一條評論,內容如下:

#Prevent the passing of active standard in handles to the calls to the script
#being tested by closing STDIN.

所以我可能在幾年前寫那篇文章的時候正在學習標準輸入檢測。我的模組可能最終使用了-t STDINand-f STDIN等,但我已經將它們切換為使用 lsof 解決這樣的問題,這樣我可以更好地看到發生了什麼。-t/-f/-p因此,當我不關閉父級中的 STDIN 時,使用目前模組(使用 lsof 或我的新(/reverted?)簡化版本使用工作正常(按預期)。

但是,我仍然想了解為什麼當父關閉 STDIN 時該文件在子程序中的 STDIN 上……

當我通過反引號從 perl 腳本中執行 perl 腳本時,(內部腳本錯誤地認為 STDIN 上有輸入)

內部腳本正確地認為有輸入 on STDIN,只是另一個打開的文件得到了文件描述符 0 (對於 perl 來說,它總是給出文件句柄STDIN)。如您所知,通過perlqx{...}...在 perl 中執行的程序從外部腳本繼承標準輸入文件描述符,就像任何其他子程序一樣。

因為內部腳本繼承了原始文件描述符0,而不是 perl STDIN文件句柄,所以這會產生緩衝問題,因為內部或外部腳本最終可能會讀取它需要的更多輸入,而不會為另一個留下任何內容。考慮這個例子:

$ echo text | perl -e '$junk=`perl -e "eof(STDIN)"`; print while <>'
$ # nothing!

只需“測試 EOF”,內部腳本就不會為外部腳本留下任何輸入。

然而,在內部腳本中進行無緩衝讀取sysread將按預期工作:

$ cat inner.pl
sysread STDIN, $d, 2
$ echo text | perl -e '$junk = `perl inner.pl`; print while <>'
xt

$$ from the other answer $$

與:your-script <&-標準輸入將被關閉。

關閉像 stdin 這樣的文件描述符從來都不是一個好主意(守護程序將它們從 重定向/dev/null,它們從不關閉它們),但在執行用 perl 或 python 等語言編寫的腳本時尤其糟糕,因為這可能會導致 stdin 最終打開(並且指腳本)而不是關閉

$ cat script.pl
seek STDIN, 0, 0;
print while <STDIN>;
$ perl script.pl <&-
seek STDIN, 0, 0;
print while <STDIN>;

發生這種情況是因為系統呼叫喜歡open(2)socket(2)返回第一個空閒文件描述符;如果標準輸入被關閉,返回的 fd 將“成為”標準輸入。

如果某些使用者從互動式 shell 呼叫您的腳本而沒有重定向,例如:

your-script with args

您的腳本將繼承 shell 的標準輸入,這將是一個很可能以讀寫模式打開的 tty 設備。

如果使用者將其呼叫為:

your-script with args < some-file

fd 0 將以只讀模式打開some-file(任何類型的;如果< /dev/pts/0是 tty 設備,那也將是一個 tty 設備;如果它是一個 fifo 文件,stdin 將顯示為來自管道;使用< /dev/null,那將是其他字元設備等)。

和:

your-script with args <> some-file

這將與上面相同,除了文件將以讀+寫模式打開,如果他們這樣做<> /dev/pts/0,那將與從終端中的互動式 shell 呼叫非重定向腳本時完全相同。

和:

your-script <&-

標準輸入將被關閉。

和:

other-cmd | your-script

stdin 在大多數 shell 中將是一個管道(與執行< named-pipeor時相同< <(cmd)),但在 ksh93 中可能是一個套接字對。

you-script &非互動式 shell 中,stdin 將是/dev/null.

output=$(your-script)or output=your-scrip``, orcmd <(your-script)中,stdin 將保持不變,但 stdout 將是一個管道。

your-script |&(ksh) 或coproc your-script(zsh, bash) 中,stdin 和 stdout 都是管道。

如果您的腳本從以下位置開始:

ssh host your-script

也就是說,通過sshdon host,那麼 stdin 和 stdout 也將是一個管道(使用rsh,那將是直接在讀+寫中的網路套接字)。

如果由 a cronorat作業啟動,stdin 可能是/dev/null, stdout 是一個管道(如果有輸出,​​最終將通過電子郵件發送)。

等等

要從腳本中檢測所有這些,不需要lsof.

檢測:

  • 標準輸入是否打開:fcntl(STDIN, F_GETFL, 0)如果標準輸入未打開,執行將失敗的操作。
  • 在哪種模式下打開(r,w,rw):檢查fcntl()上面的返回值中的O_RDONLY,O_WRONLY,O_RDWR。
  • 在標準輸入(正常、管道、設備)上打開的文件的類型:執行fstat()系統呼叫(stat STDIN在 perl 中)並從那裡的mode欄位中獲取類型。或者您可以使用 perl 的-f// … 來測試每種可能的文件類型。-d``-p
  • 對於設備文件,無論是 tty 設備,使用POSIX::isatty(STDIN)-t.

但是這些與回答這個問題幾乎沒有關係:*是否有任何東西可以從標準輸入讀取,*或者read()失敗塊或返回 EOF,為此您需要poll().

我不確定您的最終目標是什麼,但聽起來您希望您的腳本具有互動模式(使用者與之互動)和自動化模式,並根據標準輸入和/或標準輸出是。

所以這應該只是檢查-t STDIN,也許還-t STDOUT檢查stdin和/或stdout是否是tty(是否有使用者通過tty設備進行互動)。

引用自:https://unix.stackexchange.com/questions/664204