如果(且僅當)命令輸出太長時,通過尋呼機管道輸出命令的最佳方法是什麼?
我希望能夠包裝一個命令,這樣如果它的輸出不適合終端,它將自動通過尋呼機傳輸。
現在我正在使用以下 shell 函式(在 zsh 中,在 Arch Linux 下):
export LESS="-R" RET="$($@)" RET_LINES="$(echo "${RET}" | wc -l)" if [[ $RET_LINES -ge $LINES ]]; then echo "${RET}" | ${PAGER:="less"} else echo "${RET}" fi
但這並不能真正說服我。有沒有更好的方法(在健壯性和成本方面)來實現我想要的?如果它能很好地完成工作,我也對 zsh 特定的程式碼持開放態度。
*更新:*由於我問了這個問題,我找到了一個答案,它提供了一個更好的(如果更複雜的話)解決方案,它在
$LINES
將輸出管道傳輸到less
而不是全部記憶體之前緩衝大多數行。可悲的是,這也不是很令人滿意,因為這兩種解決方案都沒有考慮到長的包裹線。例如,如果上面的程式碼儲存在一個名為 的函式中pager_wrap
,那麼pager_wrap echo {1..10000}
將很長的行列印到標準輸出,而不是通過尋呼機進行管道傳輸。
我有一個為 POSIX shell 合規性編寫的解決方案,但我只在 bash 中對其進行了測試,所以我不確定它是否可移植。而且我不知道 zsh,所以我沒有嘗試讓它對 zsh 友好。您將命令輸入其中;將命令作為參數傳遞給另一個命令是一個糟糕的設計*。
當然,任何解決這個問題的方法都需要知道終端有多少行和多少列。在下面的程式碼中,我假設您可以依賴
LINES
和COLUMNS
環境變數(less
看起來)。更可靠的方法是:
- 使用
rows="${LINES:=$(tput lines)}"
andcols="${COLUMNS:=$(tput cols)}"
,如AP 建議的那樣,或- 查看 的輸出
stty size
。請注意,此命令必須將終端作為其標準輸入,因此,如果它在腳本中,並且您正在通過管道輸入腳本,則必須說stty size <&1
(在 bash 中)或stty size < /dev/tty
. 擷取其輸出更加複雜。秘訣是:該
fold
命令會像螢幕那樣打斷長行,因此腳本可以正確處理長行。#!/bin/sh buffer=$(mktemp) rows="$LINES" cols="$COLUMNS" while true do IFS= read -r some_data e=$? # 1 if EOF, 0 if normal, successful read. printf "%s" "$some_data" >> "$buffer" if [ "$e" = 0 ] then printf "\n" >> "$buffer" fi if [ $(fold -w"$cols" "$buffer" | wc -l) -lt "$rows" ] then if [ "$e" != 0 ] then cat "$buffer" else continue fi else if [ "$e" != 0 ] then "${PAGER:="less"}" < "$buffer" # The above is equivalent to # cat "$buffer" | "${PAGER:="less"}" # … but that’s a UUOC. else cat "$buffer" - | "${PAGER:="less"}" fi fi break done rm "$buffer"
要使用這個:
- 將上述內容放入文件中;假設您呼叫它
mypager
。- (可選)將其放入您的搜尋路徑的目錄中;例如,
$HOME/bin
。- 通過鍵入使其可執行
chmod +x mypager
。ps ax | mypager
在諸如or之類的命令中使用它ls -la | mypager
。如果您跳過了第二步(將腳本放入作為您的搜尋路徑的目錄中),您將不得不這樣做,其中可以是像“ ”這樣的相對路徑。
ps ax | *path_to_mypager*/mypager
path_to_mypager
.
*為什麼將一個命令作為參數傳遞給另一個命令是一個糟糕的設計?
一、美學/遵從傳統/Unix哲學
Unix 的哲學是做一件事並做好。例如,如果一個程序要以某種方式顯示數據(如尋呼機所做的那樣),那麼它也不應該呼叫產生數據的機制。這就是管道的用途。
沒有多少 Unix 程序執行使用者指定的命令或程序。讓我們看一些這樣做的:
- shell 就像在 Well 中一樣,執行使用者指定的命令是 shell 的工作;這是外殼所做的一件事。(當然我不是說 shell 是一個簡單的程序。)
sh -c "*command*"
env
,nice
,nohup
,setsid
,su
, 和sudo
. 這些程序有一些共同點——它們都是為了執行一個經過修改的執行環境的程序而存在的1。它們必須按照它們的方式工作,因為 Unix 通常不允許您更改另一個程序的執行環境;您必須更改自己的流程,然後fork
和/或exec
.1 我這裡用的 是廣義的執行環境
nice
這個詞,不僅指環境變數,還包括“ ”值、UID和GID、程序組、會話ID、控制終端、打開文件、工作目錄等程序屬性,umask
值,ulimit
s, 信號處置,alarm
計時器等
- 允許“shell 轉義”的程序。唯一想到的例子是
vi
/vim
,儘管我很確定還有其他例子。這些都是歷史文物。它們早於視窗系統甚至工作控制;如果您正在編輯文件,並且想做其他事情(例如查看目錄列表),則必須保存文件並退出編輯器才能返回 shell。現在你可以切換到另一個視窗,或者使用Ctrl
+Z
(或 type:suspend
)返回你的 shell,同時讓你的編輯器保持活動狀態,所以 shell 轉義可以說已經過時了。我不計算執行其他(硬編碼)程序以利用它們的功能而不是複制它們的程序。例如,某些程序可能會執行
diff
或sort
. (例如,有故事說,早期版本的spell
用於sort -u
獲取文件中使用的單詞列表,然後diff
- 或者也許comm
- 將該列表與字典單詞列表進行比較,並確定文件中的哪些單詞不在詞典。)二、時間問題
編寫腳本的方式是,
RET="$($@)"
在呼叫的命令完成之前,該行不會完成。因此,在生成數據的命令完成之前,您的腳本無法開始顯示數據。可能最簡單的解決方法是將數據生成命令與數據顯示程序分開(儘管還有其他方法)。三、命令歷史
- 假設您使用顯示過濾器處理的輸出執行某個命令,然後查看輸出,並決定要將輸出保存在文件中。如果您輸入了(作為假設範例)
ps ax | mypager
然後你可以輸入
!:1 > myfile
或按
↑
並適當地編輯該行。現在,如果你輸入了mypager "ps ax"
您仍然可以返回並將該命令編輯為
ps ax > myfile
,但這並不是那麼簡單。 2. 或者假設您決定ps uax
接下來要執行。如果你輸入了ps ax | mypager
,你可以做!:0 u!:*
同樣,使用
mypager "ps ax"
,它仍然是可行的,但可以說更難。 3. 另外,請查看兩個命令:ps ax | mypager
和mypager "ps ax"
。假設您在一history
小時後執行列表。ISTM,您必須mypager "ps ax"
更加努力地查看正在執行的命令是什麼。四。複雜的命令/報價
echo {1..10000}
顯然只是一個範例命令;ps ax
也好不了多少。如果你想做一些更現實的事情怎麼辦,比如ps ax | grep oracle
?如果你輸入mypager ps ax | grep oracle
它將執行
mypager ps ax
並通過grep oracle
. 因此,如果 from 的輸出ps ax
為 30 行長, 即使輸出僅為 3 行,mypager
也會呼叫。可能有一些例子會以更戲劇性的方式失敗。less``ps ax | grep oracle
所以你必須做我之前展示的事情:
mypager "ps ax | grep oracle"
但
RET="$($@)"
處理不了。當然,有一些方法可以處理這樣的事情,但他們不鼓勵。 2. 如果要擷取其輸出的命令行更加複雜怎麼辦?例如,*命令1* “*參數1* ”| *命令**2'arg* *2* ' $ ' *arg* *3* *'*
其中參數包含空格、製表符、
$
、|
、\
、<
、>
、*
、;
、&
、[
、]
、(
、)
、```甚至甚至'
和的雜亂組合"
。像這樣的命令很難正確地直接輸入到 shell 中。現在想像一下必須引用它以將其作為參數傳遞給mypager
.