리눅스 명령행 인자: 명령어 옵션과 따옴표, 옵션 끝 표시, $1, $# 받기

測試環境RHEL 系列AlmaLinux 9.8bash 5.1root 帳號
Shell$1$#--

Linux 命令列引數(command-line argument)是寫在指令名稱後面、以空白隔開並傳給程式的值。在 AlmaLinux 9 伺服器上,把選項、引號、萬用字元和 -- 傳給一個會原樣印出所收到引數的測試指令碼後發現,切分引數的是 shell,讀取選項的則是各個指令自己。

下方輸出是在 AlmaLinux 9.8 伺服器的 root 帳號以 bash 5.1 執行的原始結果。測試指令碼與範例檔案另外建立在工作資料夾中,路徑 /home/user/www 為範例值。

1️⃣ 什麼是 Linux 命令列引數?

在命令列寫下像 [指令 引數1 引數2] 這樣的內容時,shell 會以空白為界切分單字。第一個單字成為要執行的程式,其餘單字依序作為引數傳給該程式。

為了親眼看到傳入的引數,我們在工作資料夾建立了 args.sh。$0 存放執行時用的名稱,$# 存放引數個數,從 $1 起依序存放各個引數。方括號是為了看清每個引數的開頭與結尾而加上的。

#!/bin/bash
echo "\$0 = $0"
echo "\$# = $#"
i=1
for a in "$@"; do
  echo "\$$i = [$a]"
  i=$((i+1))
done

替指令碼加上執行權限後,傳入 a、b、c 三個單字執行。

./args.sh a b c
$0 = ./args.sh
$# = 3
$1 = [a]
$2 = [b]
$3 = [c]

三個單字分別進入 $1、$2、$3,$# 為 3。若在 bash 後面寫上指令碼名稱來執行,$0 會照寫下的名稱放入 args.sh。

bash args.sh a b c
$0 = args.sh
$# = 3
$1 = [a]
$2 = [b]
$3 = [c]

只有 $0 變成 args.sh,三個引數與個數都和前面的結果相同。

2️⃣ 命令列引數與指令選項

Linux 指令通常依 [指令 -選項 引數] 的順序書寫。選項改變指令的運作方式,引數則指定指令要處理的檔案、路徑或值。

ls -l /home/user/www

對 shell 而言,-l 也只是一個引數。把相同的單字傳給 args.sh 後,-l 進入 $1,路徑進入 $2。

./args.sh -l /home/user/www
$0 = ./args.sh
$# = 2
$1 = [-l]
$2 = [/home/user/www]

把 -l 當成選項來讀的是 ls。所以同一個字母在不同指令中意思不同:head 的 -n 指定要輸出的行數,ls 的 -n 則以編號而非名稱顯示擁有者。

有些選項會把緊接在後的單字當成自己的值。在 find 指令中,-type 以 f、-name 以 "*.txt" 作為自己的值,像這樣附屬於選項的值稱為選項引數。

find . -type f -name "*.txt"

這個指令最前面的 . 是不附屬於任何選項的引數,用來指定開始搜尋的資料夾。

3️⃣ Linux 指令選項的寫法

以一個連字號開頭的短選項只有一個字母,而且可以多個連寫。首先用 [ls -l -a] 列出工作資料夾中的 site 資料夾。

ls -l -a site
total 8
drwxr-xr-x 3 root root  52 Oct 28 16:00 .
drwxr-xr-x 3 root root 137 Oct 29 01:20 ..
-rw-r--r-- 1 root root  17 Oct 28 16:00 .htaccess
drwxr-xr-x 2 root root  23 Oct 28 16:00 css
-rw-r--r-- 1 root root  14 Oct 28 16:00 index.html

接著用 diff 與兩個字母連寫的 [ls -la] 的輸出比較。

diff <(ls -l -a site) <(ls -la site) && echo same
same

兩份輸出連一個字元都沒有差別。以兩個連字號開頭的長選項寫成單字,ls 的 --all 與 -a 是同一個選項,所以 [ls -l --all site] 也得到相同結果。

長選項只寫開頭部分,只要能確定是唯一一個選項就會被接受。--al 同時符合 --all 與 --almost-all 兩個選項,因此 ls 沒有輸出清單,並以結束代碼 2 結束。

ls --al site
ls: option '--al' is ambiguous; possibilities: '--all' '--almost-all'
Try 'ls --help' for more information.

需要值的選項有好幾種寫法。用 head 輸出 a.txt 的前兩行時,短選項可以把值直接接上,寫成 [-n2]。

head -n2 a.txt
alpha
beta

長選項用等號接上值,寫成 [--lines=2]。

head --lines=2 a.txt
alpha
beta

連同以空白隔開的 [-n 2] 與 [--lines 2],四種寫法都輸出了 alpha 和 beta 兩行。

ls、head、cat 都用 C 函式庫的 getopt_long 函式讀取選項。用 strings 查看這三個程式檔,每個都含有這個函式名稱。

要在 shell 中確認相同的規則,可使用 util-linux 的 getopt 指令。在 -o 後面寫短選項、在 -l 後面寫長選項,字母後面的冒號表示該選項需要值。

getopt -o lan: -l all,lines: -- -la -n 2 --all a.txt
 -l -a -n '2' --all -- 'a.txt'

連寫的 -la 被拆成 -l 與 -a,-n 2 被合併為 -n 與值 2。不是選項的 a.txt 被移到最後的 -- 之後。

4️⃣ 用引號括住的命令列引數與萬用字元

含有空白的檔名必須用引號括住,才會作為一個引數傳入。

./args.sh "my file.txt" report.txt
$0 = ./args.sh
$# = 2
$1 = [my file.txt]
$2 = [report.txt]

不加引號時,my 和 file.txt 被分開,$# 變成 3。若這樣傳給 ls 或 cat,就會去找名為 my 和 file.txt 的兩個檔案。

./args.sh my file.txt report.txt
$0 = ./args.sh
$# = 3
$1 = [my]
$2 = [file.txt]
$3 = [report.txt]

萬用字元會在程式收到之前由 shell 換成檔名。在工作資料夾傳入 *.txt 後,以 .txt 結尾的四個檔案依名稱順序傳入。

./args.sh *.txt
$0 = ./args.sh
$# = 4
$1 = [a.txt]
$2 = [b.txt]
$3 = [my file.txt]
$4 = [report.txt]

用引號括住的 "*.txt" 不會被 shell 替換,原封不動成為一個引數。像 find 的 -name 這種由程式自己比對模式的選項,就要這樣用引號括住再傳入。

./args.sh "*.txt"
$0 = ./args.sh
$# = 1
$1 = [*.txt]

空的引號也會作為一個空引數傳入。執行 [./args.sh '' b] 後,$# 為 2,$1 為空。

5️⃣ 指令選項結束標記(--)

像名為 -n 的檔案這樣以連字號開頭的引數,會被指令當成選項讀取。在只放了這個檔案的 dash 資料夾執行 [ls -n] 時,ls 不把 -n 當檔名,而是當成編號輸出選項,並列出了資料夾內容。

ls -n
total 4
-rw-r--r-- 1 0 0 10 Oct 28 16:00 -n

寫在 -- 之後的引數即使以連字號開頭,也不會被當成選項。[ls -- -n] 只輸出了名為 -n 的檔名。

ls -- -n
-n

即使在同一個指令中,-- 前後相同的字元也會被讀成不同的東西。把 -n 傳給 cat 兩次後,前面的 -n 被讀成行號選項,-- 後面的 -n 被讀成檔名,輸出了加上行號的檔案內容。

cat -n -- -n
     1	dash file

也可以加上路徑寫成 ./-n,這時引數不以連字號開頭,會被讀成同一個 -n 檔案。

把 -- 當成選項結束來讀,也是指令這一側的規則。傳給不讀選項的 args.sh 時,-- 也原樣進入了 $1。

./args.sh -- -a
$0 = ./args.sh
$# = 2
$1 = [--]
$2 = [-a]

POSIX 公用程式慣例規定,不是選項引數的第一個 -- 要被當成選項的結束。因此在遵循這套慣例的指令中,以 - 開頭的檔名要寫在 -- 之後。

6️⃣ 在 shell 指令碼中接收命令列引數

shell 指令碼透過位置參數與特殊參數讀取收到的引數。C 程式的 main(int argc, char *argv[]) 也接收相同的引數,兩者並排如下。

參數存放的值C 語言 main 函式
$0執行的指令碼名稱argv[0]
$1 ~ $9第一到第九個引數argv[1] ~ argv[9]
${10}第十個起加大括號argv[10]
$#引數個數、不計 $0argc - 1
"$@"全部引數、逐一分開從 argv[1] 到最後
"$*"以空白串成一個字串—

Python 程式會以 sys.argv 清單接收相同的引數。用 [python3 -c] 傳入三個引數後,用引號括住的 my file.txt 進入清單的同一格,第一格則是 -c。

python3 -c 'import sys; print(sys.argv)' a "my file.txt" -l
['-c', 'a', 'my file.txt', '-l']

"$@" 與 "$*" 用雙引號括住時結果不同。為了看出這個差別,我們建立了依序輸出這兩種寫法的 star.sh。

#!/bin/bash
echo 'with "$@"'
printf '  [%s]\n' "$@"
echo 'with "$*"'
printf '  [%s]\n' "$*"

以 a、"b c"、d 三個引數執行 star.sh。

./star.sh a "b c" d
with "$@"
  [a]
  [b c]
  [d]
with "$*"
  [a b c d]

"$@" 把三個引數分開傳遞,"$*" 則以空白串成一個傳遞。

因此,要把收到的引數原樣傳給其他指令時,使用 "$@"。

shift 會丟掉 $1,把其餘引數各往前移一格。用在先讀一個選項、再把其餘當成檔案清單處理的指令碼中。

我們建立了在 shift 前後輸出 $1 與 $# 的 shift.sh。

#!/bin/bash
echo "before shift: \$1 = $1, \$# = $#"
shift
echo "after shift:  \$1 = $1, \$# = $#"

以 -v、a.txt、b.txt 三個引數執行 shift.sh。

./shift.sh -v a.txt b.txt
before shift: $1 = -v, $# = 3
after shift:  $1 = a.txt, $# = 2

第十個引數起要像 ${10} 這樣用大括號括住。我們建立了並排輸出 $10 與 ${10} 的 ten.sh。

#!/bin/bash
echo "\$10 = $10"
echo "\${10} = ${10}"

把 a 到 k 共 11 個引數傳給 ten.sh。

./ten.sh a b c d e f g h i j k
$10 = a0
${10} = j

$10 輸出為 $1 後面接 0 的 a0,${10} 則輸出第十個引數 j。

🔲 命令列引數模擬器

在下方的模擬器中,可以把引號、萬用字元和 -- 傳給 args.sh,也能自己輸入指令,看看 ls 與 getopt 如何讀取選項,以及 star.sh 與 shift.sh 如何接收引數。

命令列引數模擬器bash 5.1
[root@server work]#
這是只在瀏覽器中運作的虛擬 AlmaLinux 伺服器,args.sh 會連同編號輸出收到的引數,按 ↑↓ 可叫出先前用過的指令。

🔢 FAQ & 推薦內容

引數與環境變數合計長度的上限可用 [getconf ARG_MAX] 確認,在 AlmaLinux 9.8 伺服器上是 2097152 位元組。超過這個上限,程式不會執行,並出現 [Argument list too long] 錯誤。檔案非常多時,不要用萬用字元一次傳入,而是用 find 的 -exec 或 xargs 分批傳入。

引數(argument)是執行指令時實際傳入的值,參數(parameter)則是接收端用來接住這個值的名稱。在 shell 指令碼中,由 $1、$2 這類位置參數接收傳入的引數。

bash 內建的 getopts 會逐一讀取 -v 這類短選項。它不讀長選項,所以連 --verbose 這類長選項也要接收時,先用上面使用過的 getopt 指令整理引數。

ℹ️ 聯盟行銷說明
本站內容包含聯盟行銷連結。訪客透過這些連結購買商品或服務時,本站會從賣家獲得佣金。在這個過程中,購買者支付的金額(遇到活動折扣時金額會降低↓)並不會因此提高。刊登的價格、折扣與庫存資訊以撰寫當時為準,可能與實際情況不同,購買前請向賣家做最後確認。商品的挑選與評價依本站自訂的標準撰寫,是否支付佣金不會影響介紹順序或評價內容。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Prove your humanity: 0   +   10   =