Shell

如果(且僅當)命令輸出太長時,通過尋呼機管道輸出命令的最佳方法是什麼?

  • May 19, 2021

我希望能夠包裝一個命令,這樣如果它的輸出不適合終端,它將自動通過尋呼機傳輸。

現在我正在使用以下 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 友好。您將命令輸入其中;將命令作為參數傳遞給另一個命令是一個糟糕的設計*。

當然,任何解決這個問題的方法都需要知道終端有多少行和多少列。在下面的程式碼中,我假設您可以依賴LINESCOLUMNS環境變數(less看起來)。更可靠的方法是:

  • 使用rows="${LINES:=$(tput lines)}" and cols="${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*/mypagerpath_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值, ulimits, 信號處置,alarm計時器等

  • 允許“shell 轉義”的程序。唯一想到的例子是vi/ vim,儘管我很確定還有其他例子。這些都是歷史文物。它們早於視窗系統甚至工作控制;如果您正在編輯文件,並且想做其他事情(例如查看目錄列表),則必須保存文件並退出編輯器才能返回 shell。現在你可以切換到另一個視窗,或者使用Ctrl+ Z(或 type :suspend)返回你的 shell,同時讓你的編輯器保持活動狀態,所以 shell 轉義可以說已經過時了。

我不計算執行其他(硬編碼)程序以利用它們的功能而不是複制它們的程序。例如,某些程序可能會執行diffsort. (例如,有故事說,早期版本的spell 用於sort -u獲取文件中使用的單詞列表,然後diff- 或者也許comm- 將該列表與字典單詞列表進行比較,並確定文件中的哪些單詞不在詞典。)

二、時間問題

編寫腳本的方式是,RET="$($@)"在呼叫的命令完成之前,該行不會完成。因此,在生成數據的命令完成之前,您的腳本無法開始顯示數據。可能最簡單的解決方法是將數據生成命令與數據顯示程序分開(儘管還有其他方法)。

三、命令歷史

  1. 假設您使用顯示過濾器處理的輸出執行某個命令,然後查看輸出,並決定要將輸出保存在文件中。如果您輸入了(作為假設範例)
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 | mypagermypager "ps ax"。假設您在一history小時後執行列表。ISTM,您必須mypager "ps ax"更加努力地查看正在執行的命令是什麼。

四。複雜的命令/報價

  1. 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.

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