前言
前陣子撰寫了 Git Hook 腳本(也因此有 #5 ),不過直到最近才發現忘記給它執行權限 ...,導致前些陣子提交時其實都沒有檢查到 Orz
這次加上執行權限後發現還是無法正常運作,於是研究了一下 Bash 並紀錄這次遇到的問題。
多數的 shell 指令都會加上一些參數來去執行,而 word splitting 就是決定參數是哪些的過程中的一部分。
原文提供了一個腳本可以把 shell 建構好的參數一個個印出來。
#!/usr/bin/env bash
printf "%d args:" "$#"
printf " <%s>" "$@"
echo
使用情況大致如下:
$ ./args hello world "how are you?"
3 args: <hello> <world> <how are you?>
Word splitting 的執行順位很後面 [1],在多數種類的展開(expansion)後面,而結果是根據一個特別的變數 IFS - Internal Field Separator 來決定要以什麼字元做切割,當沒有特別設定 IFS 時,預設是 <space>, <tab>, <newline>,舉例來說:
bash-4.2$ var="This is a variable"
bash-4.2$ ./args $var
4 args: <This> <is> <a> <variable>
若是將 IFS 換成別的字元:
bash-4.2$ IFS=a
bash-4.2$ ./args $var
4 args: <This is > < v> <ri> <ble>
遇到的問題
在 Git Hook 腳本中,將 clang-format 的選項用變數存了起來:
CLANG_FORMAT = $(which clang-format)
OPTIONS="-style=\"{BasedOnStyle: LLVM, IndentWidth: 2}\""
$CLANG_FORMAT $OPTIONS test.c
在下面執行 clang-format 時就會遇到 word splitting 的問題,整串會被解讀成:
++ which clang-format
+ CLANG_FORMAT=/bin/clang-format
+ OPTIONS='-style="{BasedOnStyle: LLVM, IndentWidth: 2}"'
+ /bin/clang-format '-style="{BasedOnStyle:' LLVM, IndentWidth: '2}"' test.c
No such file or directory
No such file or directory
No such file or directory
Invalid value for -style, using LLVM style
OPTIONS 被展開後,經過 word splitting 以 <space> 切開,導致參數變成了多個,這邊使用上面的 args 來觀察:
$ ./args $OPTIONS
4 args: <-style="{BasedOnStyle:> <LLVM,> <IndentWidth:> <2}">
原本預期只有兩個參數的指令,變成了四個參數,導致上面多了三個 "No such file or directory" 的錯誤,而第四個錯誤是 clang-format 去解析 -style 時所發生的 [2]。
解決方法
原始的解法是利用 array 把參數當成一個字詞包起來,並將其展開:
$ OPTIONS=(-style="{BasedOnStyle: LLVM, IndentWidth: 2}")
$ ./args "${OPTIONS[@]}"
1 args: <-style={BasedOnStyle: LLVM, IndentWidth: 2}>
後來在撰寫這篇筆記才發現 clang-format 的說明 -style="{key: value, ...}" 中的雙引號是為了讓 shell 判斷是一整個字詞(詳見此),不要將其切開,並非是自身 parser 的規定。如此一來,第二種解法是較為簡單且清楚的,只需要在展開變數時用雙引號來壓過 word splitting 的效果即可。
$ OPTIONS="-style={BasedOnStyle: LLVM, IndentWidth: 2}"
$ ./args "$OPTIONS"
1 args: <-style={BasedOnStyle: LLVM, IndentWidth: 2}>
Misc
為了做各種實驗發現一個蠻不錯的選項可以設定:set -x 可以設定 shell 的選項,而 x 的功能是 "Print commands and their arguments as they are executed." 也就是經過 shell 解析後的最後結果會被印出來,像是有變數的話就會是以展開後的形式印出。
此外若是一個腳本的話可以用以下方法:
bash-4.2$ cat t.sh
#!/usr/bin/env bash
subject="word1 word2"
args="-s "$subject""
ls $args
ls "$args"
bash-4.2$ bash -x t.sh
+ subject='word1 word2'
+ args='-s word1 word2'
+ ls -s word1 word2
ls: cannot access word1: No such file or directory
ls: cannot access word2: No such file or directory
+ ls '-s word1 word2'
ls: invalid option -- ' '
Try 'ls --help' for more information.
- [1]: The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion. (From the bash man page) ↵
- [2]:
-style 的形式有三種,"{: , ...}", "<style name>", "file",判斷式可參考 llvm::Expected<FormatStyle>::getStyle ↵
Ref
前言
WordSplitting
多數的 shell 指令都會加上一些參數來去執行,而 word splitting 就是決定參數是哪些的過程中的一部分。
原文提供了一個腳本可以把 shell 建構好的參數一個個印出來。
使用情況大致如下:
Word splitting 的執行順位很後面 [1],在多數種類的展開(expansion)後面,而結果是根據一個特別的變數 IFS - Internal Field Separator 來決定要以什麼字元做切割,當沒有特別設定 IFS 時,預設是 <space>, <tab>, <newline>,舉例來說:
若是將 IFS 換成別的字元:
遇到的問題
在 Git Hook 腳本中,將
clang-format的選項用變數存了起來:在下面執行
clang-format時就會遇到 word splitting 的問題,整串會被解讀成:OPTIONS 被展開後,經過 word splitting 以 <space> 切開,導致參數變成了多個,這邊使用上面的
args來觀察:原本預期只有兩個參數的指令,變成了四個參數,導致上面多了三個 "No such file or directory" 的錯誤,而第四個錯誤是
clang-format去解析-style時所發生的 [2]。解決方法
原始的解法是利用 array 把參數當成一個字詞包起來,並將其展開:
後來在撰寫這篇筆記才發現
clang-format的說明-style="{key: value, ...}"中的雙引號是為了讓 shell 判斷是一整個字詞(詳見此),不要將其切開,並非是自身 parser 的規定。如此一來,第二種解法是較為簡單且清楚的,只需要在展開變數時用雙引號來壓過 word splitting 的效果即可。Misc
為了做各種實驗發現一個蠻不錯的選項可以設定:
set -x可以設定 shell 的選項,而x的功能是 "Print commands and their arguments as they are executed." 也就是經過 shell 解析後的最後結果會被印出來,像是有變數的話就會是以展開後的形式印出。此外若是一個腳本的話可以用以下方法:
-style的形式有三種,"{: , ...}", "<style name>", "file",判斷式可參考llvm::Expected<FormatStyle>::getStyle↵Ref