關於在 bash 中檢查所需指令和介面的討論

5 min

language: ja bn en es hi pt ru zh-cn zh-tw

大家好,我是無能。
結束了在公司的最後一個工作日,收到了同公司女孩送的巧克力,在離職前不久被帶去吃了烤肉,最後和一位做了25年網路工作的人,聊了聊他過去和現在的家庭網路環境,我也聊了聊我自己的家庭伺服器相關的事情,度過了愉快的時光。
我認為我已經盡力做到了我這個不成熟的人所能做的事情,但我還有很多不足之處,對此感到抱歉。


我每天都感受到 shell script 的強大。
當然,其可移植性較低,確實無法像目前 FreeBSD 預設 shell csh 那樣進行函數式編寫,這個問題是不可否認的。
然而,我認為 shell script 實際上在於其介面處理的簡易性。

C 語言與 Shell Script 的介面

例如,假設在 shell script 中執行以下 echo 指令。

echo "test"

這相當於 C 語言中的標準輸出。

#include <stdio.h>

int main() {
    const char *message = "test";

    // 將訊息輸出到標準輸出
    fprintf(stdout, "%s\n", message);

    return 0;
}

如果想輸出到標準錯誤輸出,則進行如下重定向:

echo "testerr" 1>&2

而在 C 語言中,則相當於以下程式碼。

#include <stdio.h>

int main() {
    const char *message = "testerr";

    // 將訊息輸出到標準錯誤輸出
    fprintf(stderr, "%s\n", message);

    return 0;
}

而且,大多數 GNU/Linux 指令執行失敗時的回傳值,由於過去開發者的精心設計,使得判斷條件的測試變得容易,如果將其視為腳本語言來使用,將會非常有效。
然而問題是,這只有在可以使用 shell script 的環境下才能受益。
即使以其他語言編寫類似的程式碼,其通用性仍然有效,但指令在某種程度上是作為語言中的函式庫機制運作的,我認為這也是其通用性較差的原因。
然而,我認為能夠如此簡潔地操作檔案描述符是非常強大的。

else 也只需連接兩個管道。 ※寫完後才想到,嚴格來說這並不是 else 呢 ^^;
它只是在回傳值不為 0 時,執行類似 else 的操作。

$ curl aa || echo "wtf"
curl: (6) Could not resolve host: aa
wtf

如果我在國中時期就知道這麼方便的東西,那該有多好啊。
我希望任何網站都能提供更多學習 shell script 的機會。

話說回來,現在回想起來,當時 GNU/Linux 的日文環境相當困難,日文輸入的預測轉換也達不到實用水平,所以現在的便利性或許是因為 Chrome OS 的普及,使得 OSS 日文輸入法得以蓬勃發展。
我記得在國中時期,有個古怪的朋友想要一台安裝了 UbuntuThinkPad,然後是我幫他設定的...

那麼,如何檢查指令呢?

在 Bash shell script 中編寫指令檢查時,我一直在想有沒有更簡潔易讀的寫法。

話雖如此,將原本就很簡潔的陣列透過 for 迴圈傳遞當然很方便,但看看陣列儲存時的寫法。

#!/bin/bash
cmds=(aa bb cc)
for i in ${cmds[@]}; do
  command -v $i 1> /dev/null || echo "Please install $i"
done

使用 () 將指令作為陣列儲存到 cmds 中,並使用 ${cmds[@]} 展開陣列。
但這樣一來,如果指令增加,每次都要在 cmds=() 中添加,就會讓人覺得真的有必要寫在這裡嗎?
在其他語言中,依賴關係的資訊通常會集中在 Cargo.tomldeno.jsongo.modGemfile,以及常被嫌棄的 requirements.txt 等檔案中。
如果能有一個像那樣彙整的檔案,應該會很方便。

因此,我嘗試了這樣的方式。

#!/bin/bash
cat ./cmd | xargs which 1> /dev/null || echo "Please install cmd"

那麼,cmd 檔案會是什麼樣子呢?

$ cat cmd
ls
pwd
cargo

其實我本來想用 command 指令來檢查,但 command 是作為 shell 的內部指令運作,無法傳遞給 xargs

$ which command
which: no command in (/home/haturatu/.rbenv/bin:/home/haturatu/.rbenv/shims:/home/haturatu/.deno/bin:/home/haturatu/.cargo/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/opt/android-sdk/platform-tools:/var/lib/flatpak/exports/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/lib/plan9/bin:/home/haturatu/.local/bin:/home/haturatu/.local/bin:/home/haturatu/Android/Sdk/cmdline-tools/tools/bin:/home/haturatu/Android/Sdk/platform-tools:/home/haturatu/Android/Sdk/emulator:/home/haturatu/Android/Sdk/tools:/home/haturatu/Android/Sdk/tools/bin:/home/haturatu/Android/Sdk/cmdline-tools/tools/bin:/home/haturatu/Android/Sdk/platform-tools:/home/haturatu/Android/Sdk/emulator:/home/haturatu/Android/Sdk/tools:/home/haturatu/Android/Sdk/tools/bin:/home/haturatu/.npm-global/bin:/home/haturatu/go/bin)

如果我理解錯了,很抱歉,但我認為當透過 xargs 傳遞時,它會作為一個獨立於執行 bash 程序的 xargs 程序運行,因此無法識別。
因此,雖然無法將 which 失敗的指令傳遞給 Please install cmd,但在那之前 which 的錯誤輸出會顯示,所以我認為這不是必需的。
※寫完後才想到,如果用 xargs /bin/bash command -v 的話或許可以...

將這些指令群組放在另一個檔案中還有一個好處。
可以從這個檔案一次性安裝。

cat cmd | xargs -I {} sudo pacman -S --noconfirm {}

對於 apt,也可以使用 -y 選項來實現。


總之,姑且不論我是否真的會使用,我將其作為一個想法提出。
那麼,下次再見。請多指教。(從年終總結的文章開始,不知不覺寫了好幾篇文章,這一定是我的錯覺。)

Related Posts