邊緣情況 - 在 perl 中檢測 STDIN 上的輸入
我不知道如何問這個問題,我什至不確定這是問這個問題的地方。它似乎相當複雜,我對正在發生的事情沒有完全了解。坦率地說,這就是我發帖的原因 - 以獲得一些幫助來解決這個問題。我的最終目標是學習,而不是解決我的整體問題。我想了解我何時會遇到我將要描述的情況以及為什麼會發生。
我有一個我一直在開發的 perl 模組。它所做的一件事是它檢測是否有標準輸入(無論是通過管道還是通過重定向(即
<
))。為了擷取重定向,我對各種情況採用了一些不同的檢查。其中之一是在輸出中尋找
0r
文件描述符。lsof
它工作得很好,我在很多腳本中使用我的模組都沒有問題,但是我有 1 個案例,我的腳本認為它在 STDIN 上得到輸入,但它不是 - 它與我得到的內容有關lsof 輸出。以下是我將案件範圍縮小到的條件,但這些並不是全部要求 - 我遺漏了一些東西。無論如何,這些條件似乎是必需的,但請對我的直覺持保留態度,因為我真的不知道如何在玩具範例中實現它 - 我已經嘗試過 - 這就是為什麼我知道我錯過了某物:
- 當我通過反引號從 perl 腳本中執行 perl 腳本時,(內部腳本認為它已在 STDIN 上故意輸入輸入,而實際上它沒有 - 儘管我應該指出我不知道它是否是實際打開該句柄的父或子)
- 輸入文件被提供給駐留在子目錄中的內部腳本呼叫
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 的文件,旨在附加到
@INC
perl 路徑,但我不知道其他作業系統是否提供類似的機制。我想更多地了解該文件何時存在/打開以及何時關閉。我可以關閉它嗎?似乎解釋器可能已經讀入了文件內容 - 那麼為什麼它作為打開的文件句柄在我的腳本中徘徊?為什麼它在標準輸入上?當我實際重定向自己的文件時,在這種情況下會發生什麼?在我不知道的某些情況下,子程序是否以某種方式從父程序繼承了它?
更新:我想出了在子腳本的腳本執行期間使 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 STDIN
and-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-pipe
or時相同< <(cmd)
),但在 ksh93 中可能是一個套接字對。在
you-script &
非互動式 shell 中,stdin 將是/dev/null
.在
output=$(your-script)
oroutput=
your-scrip``, orcmd <(your-script)
中,stdin 將保持不變,但 stdout 將是一個管道。在
your-script |&
(ksh) 或coproc your-script
(zsh, bash) 中,stdin 和 stdout 都是管道。如果您的腳本從以下位置開始:
ssh host your-script
也就是說,通過
sshd
onhost
,那麼 stdin 和 stdout 也將是一個管道(使用rsh
,那將是直接在讀+寫中的網路套接字)。如果由 a
cron
orat
作業啟動,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設備進行互動)。