3

【笨問題】CLI 參數為什麼有時要加 "--"? POSIX 參數慣例的冷知識

 1 year ago
source link: https://blog.darkthread.net/blog/posix-args-convension/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

【笨問題】CLI 參數為什麼有時要加 "--"? POSIX 參數慣例的冷知識-黑暗執行緒

講到指令工具參數,Windows 老人的印象有可能還停留 /?、/S 之類的斜線表示法,例如:

DIR /S D:\TEMP
XCOPY D:\Data E:Data /S /E /H

但如果有追隨近年的開發主流,會發現 Windows 以外的世界,CLI 工具其實都在用另一套差不多的參數表示法:

git merge --no-ff -m "commit message" fix/crash-issue
npm install sax --force
curl -O --insecure --header 'Host: www.example.com' -I https://192.168.1.1/file.html
npx webpack init ./my-app --force --template=default

而隨著擁抱開源,近年微軟新推出的 CLI 工具也漸漸向 Linux 看齊:

# .NET SDK
dotnet publish -c Release -r win-x64 --no-self-contained -p:PublishSingleFile=true
# Chocolatey
choco install -y --no-progress git.install
# winget
winget install --id Microsoft.PowerToys --version 0.15.2

這套參數語法裡有個讓我迷惑的符號 "--",挺常在文件出現,但我一直沒有 100% 確定用法及使用時機,只知拿香跟著拜:

Fig1_638172151280014234.png

Fig2_638172151281840962.png

最近要自己寫 CLI 打算遵循這套參數做法,花了點時間研究,這才恍然大悟。

歸納這套參數語法有個共同特色,基本結構可分為[程式] [動作命令/Command] [選項/Option] [主參數/Argument]四塊,[程式]與[命令]固定會在最前面(簡單工具則不需要命令,例如 curl),[主參數]與[選項]順序則可彈性組合搭配。

git merge --no-ff -v -m "message" feature/docker 為例,--no-ff、-m 是合併選項,而 feature/docker 則是合併對象參數(要合併的分支)。選項有兩種格式:-單一字元(例如 -v、-m) 或 --英文字(例如 --no-ff),選項後方有時會接參數,例如 -m "message";至於參數(本例中的 feature/docker,要合併的分支名稱)可以放在最後面,但也可以放在前面,寫成 git merge feature/docker --no-ff -m "message"也 OK。

這套 CLI 通用的輸入參數語法規則,術語為 POSIX 參數語法慣例

  1. 選項(Option)參數以 - 起首
  2. 選項名稱為單一字元(文數字)
  3. 有些選項需包含額外參數(Argument),例如:-o output-file-path-m "message"
  4. 選項與其參數的分隔符號(空白)可加可不加,例如:-o foo 等於 -ofoo
  5. 可以用一個 - 接多個無參數選項,例如:-abc 相當於 -a -b -c
  6. 選項"通常"在非選項參數(如 Git 案例中的 feature/docker)前方 (註:此點非強制規定,許多 CLI 程式允許選項放後面)
  7. "--" 用來區隔選項及非選項參數,-- 後方的所有內容都視為非選項參數(即使加了 - 符號)
  8. 選項可任意順序排列,並可出現多次,如何解析由程式決定
  9. GNU 新增長版選項(Long Option)規則,使用 -- 串接一到三個英文字作為選項名稱,多英文字以 - 連接,例如: --name、--no-self-contained,或者英文字也可以用縮寫(例如:--no-ff,ff == Fast Forward)
  10. 長版選項可以寫成 --name=value 接收額外參數

理解此規則,我們就不難想出為什麼會需要 "--"。

舉個簡單例子,假設有個將 JSON 表格轉為 CSV 的 CLI 工具,用 json2csv data.json 解析 data.json 轉為 CSV 顯示於螢幕。程式支援 -f 選項,可沿用原檔名將結果存成 data.csv;但程式也支援自訂 CSV 檔名 -f result.csv。由於 -f 後方可接自訂檔名也可以省略,若寫成 json2csv -f data.json,程式就必須判斷 data.json 是給 -f 的選項參數,還是非選項參數(要處理的 JSON 檔名)。當然,這個案例加幾行邏輯自動判斷 data.json 到底是 -f 的參數還是 JSON 檔名不是難事,但更簡單且保證不出錯的方法是加上 -- 把 data.json 隔開(json2csv -f -- data.json),明確告知 -- 後面都是非選項參數,不留半點模糊空間。

搞懂這點,回頭看文章開頭的兩張圖,心中不再有半點疑惑,讚!

2023-04-17 更新:本文以 GNU 的 POSIX 慣例 (GNU 版 getopt 指令的處理邏輯) 為準,與 POSIX.1-2017 / IEEE Std 1003.1-2017 規格 有些差異,例如:--name-of-option 這種 -- 接英文字的選項表示法是 GNU 新增的,IEEE 標準對於 -o foo、-ofoo 間的空白處理也有更嚴格的規定。文件也有提GNU 版是 POSIX 標準的擴充版 This is not what POSIX specifies; it is a GNU extension.,感謝讀者 Explorer 補充。

and has 5 comments

Comments

# 2023-04-17 01:57 AM by Explorer

作者有些地方解釋得不太正確,這邊給予補充/糾正: 1. 當選項後面可加可不加參數的時候,POSIX 的慣例是選項的字母跟選項的參數中間不得有空格,所以假如文中的-f選項可帶可不帶參數的時候…… -fresult.csv # 這樣寫才是正確的 -f"result.csv" # 這樣寫也正確,shell會自動把引號去除 -f result.csv # 這樣寫的話剖析器會認為-f 不帶參數,無論有沒有"--"區隔。

2. "--" 主要的目的是因應參數有可能以橫線記號開頭,例如以橫線開頭的檔名或是需要指定一個負號數字,範例: mkdir -m755 /root/foo/bar # 這會建立一個 /root/foo/bar 資料夾 mkdir -- -m755 /root/foo/bar # 這會建立兩個資料夾,一個為 -m755(檔名開頭為橫線),另一個為 /root/foo/bar

3. 如果命令選項後面的參數在該命令的規則裡是不可省略的話,選項的字母跟選項參數中間就可有空格,但也可以不用。以git commit 的-m選項為例,-m一定要指定訊息,但下面兩種寫法效果會相同: git commit -m"My message" git commit -m "My message" 寫shell script 的時候會建議使用前面的形式(選項字母跟選項參數中間不加空格)。

# 2023-04-18 03:43 AM by Forger

https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html POSIX.1-2017 因為歷史因素的例外條件:(標準規格為了顧及向下相容性是容許變通的,不過宗旨還是希望未來的應用程式得完群遵守 12.2 語法規範,過去的應用程式就算了,以免破壞既有工具的相容性) 12.1 第 2 點載明,選用性選項 -c option_argument 的 -c 選項 以及 option_argument 選項參數 是用 <blank> 空格分隔,唯一一項例外是當 選項參數 [option_argument] 用方括弧包夾時,這反映了一種狀況,例如 [-f[option_argument]] 的 -f 選項跟 [option_argument] 選項參數 是緊鄰在一起的(沒有用空格分開),用來表示選用性 選項參數;然而,如果是必須性的選項參數,例如 [-c option_argument] 的 option_argument 是緊接著到下一個參數(用空格分開)。依據 12.2 語法規範,如果 選項 跟它的 選項參數 是分開的兩個獨立參數時,選項參數 就不能是選擇性的,所以不能是 [-c [option_argument]] 這樣寫。不過因為歷史因素,有以下幾點例外: 12.1 第 2 點 a 項目,依據 [-c option_argument] 用法格式的描述,依循標準的應用程式,應該把 -c 選項 以及 option_argument 選項參數,用兩個分開的獨立參數表示(也就是要用空格隔開,不能緊鄰在一起)。然而依循標準的實作,也應該允許 選項 跟它的 選項參數 是緊鄰在一起的,不必用空格分隔(也就是容許 [-coption_argument] 或 [-c"option argument"] 假如 選項參數 含有空格字元,雙引號會被 SHELL 移除)。 12.1 第 2 點 b 項目,依據 [-f[option_argument]] 用法格式的描述,依循標準的應用程式,應該把 -f 選項 以及 [option_argument] 選項參數,緊鄰的擺放在一起,不必用空格分隔。如果指令參數只有提供 -f 選項,則按照用法格式的描述,視為省略了 [option_argument] 選項參數;並且不能將緊接著的下一個參數,當成是 [option_argument] 選項參數。(也就是說不會有 [-f [option_argument]] 的情況發生,例如 -f ABC 的 -f 選項省略了 選項參數,而不是把 ABC 當作是 -f 選項的 [option_argument] 選項參數) 結論:規範不是盲目遵循,例如規則說不能怎樣,就不能做,那就類似程式語言當中的 Strict 嚴謹模式,但規範中有提到例外,也就是容許變通,不表示這樣做出來的應用程式,就沒有達到依循標準的目的。因此,用法格式的描述 [-c option_argument] 這樣寫,就表示你要 -cXXXX 或是 -c XXXX (用空格隔開),都是合法的。但是 [-f[option_argument]] 就不能這麼隨興,只能 -fXXXX 這樣使用,或是 -f 單獨使用,即便 -f XXXX (用空格隔開) 也當作省略了 [option_argument] 選項參數,而不是將 XXXX 當作是 選項參數。 12.2 語法規範 第 4 點,所有選項應該用 - 減號開頭。 12.2 語法規範 第 5 點,一般選項,例如 [-a][-b][-d|-e] 選項,不含有 option_argument 選項參數 的 選項,在使用一或多個此類選項時,最後一個選項可以緊接著另一個帶有 option_argument 選項參數 的 選項,只能有這麼一個參數在,那麼這些選項將能夠被接受並作為群組合併在一起,並且開頭只有一個 - 減號。 (也就是說 -a -b -d -c XXXX 一起用,可以簡化為 -abdc XXXX,而不能 -abcd XXXX 這樣使用,-c 的 c 選項只能在群組內最後一個選項,因為 -d 並沒有 option_argument 選項參數) 範例指令:netstat -nlp、tar -cvvf file.tar、git clean -xdf。 12.2 語法規範 第 10 點,第一個遇到的 -- 兩個減號 參數,如果不是 option_argument 選項參數,那它就是 選項 解析的終止條件。(換句話說 -c -- -- -? 第一個 -- 被當成是 -c 選項的 選項參數,第二個 -- 才是終止條件,-? 不視為是選項) 長參數、短參數:長參數 開頭有兩個減號,例如 [--file option_argument],反之,一般選項只有一個減號,就是 短參數,例如 [-f]。在正式常用的 Linux 工具,像是 man tar 查詢 tar 指令的說明文件,裡面會列出 -f, --file=ARCHIVE 這樣的描述,-f 短參數,--file=長參數檔名。然而 長參數 的 選項參數 不見得要用 = 分隔,也可以用空格分隔,雖然說明文件沒有這樣寫,但是可以這樣用。 此外,像是指令 cd - 或 su -,它們的 - 減號 是程式自己定義的,man su 說明文件的定義則是 -, -l, --login,而 ~ 這個家目錄的符號是 SHELL 定義的。像本篇的 -- 兩個減號,也是 SHELL 定義出來的功能。 12.2 語法規範 第 13 點,像是 tar | gzip - 或 echo -n '計算雜湊值,結尾不含換行' | sha256sum - 則表示從標準輸入的管線讀取內容,sha256sum 不加上 - 減號 效果相同。 12.2 語法規範 第 14 點,如果從第 3 點到第 10 點能夠判斷出這個參數是屬於選項的話,並且如果是選項群組的話,那麼它們即便沒有在開頭加上 - 減號,也是合法的選項規則。 (這也就是為什麼 tar cvvf file.tar 或 ps aux 也能運作的原因,即便不符合 第 4 點 規範,沒在開頭加上 - 減號) https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial https://www.nuget.org/packages/System.CommandLine 微軟官方的教學,使用此套件完美實現 Linux 以上所有長參數、短參數功能,微軟自己的 .NET CLI 也是使用此套件,命令提示字元並沒有 -- 兩個減號的機制,所以 dotnet run -- --file scl.runtimeconfig.json 是模擬出來的。 以上是從 Windows 平台,跨越到 Linux 平台 所必備的基本知識。

Post a comment

Comment
Name Captcha 39 + 0 =

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK