Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WordSplitting Problem in Bash #7

Open
Naetw opened this issue Dec 17, 2018 · 0 comments
Open

WordSplitting Problem in Bash #7

Naetw opened this issue Dec 17, 2018 · 0 comments

Comments

@Naetw
Copy link
Owner

Naetw commented Dec 17, 2018

前言

前陣子撰寫了 Git Hook 腳本(也因此有 #5 ),不過直到最近才發現忘記給它執行權限 ...,導致前些陣子提交時其實都沒有檢查到 Orz
這次加上執行權限後發現還是無法正常運作,於是研究了一下 Bash 並紀錄這次遇到的問題。

WordSplitting

多數的 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

@Naetw Naetw changed the title Bash 筆記 Bash Dec 17, 2018
@Naetw Naetw changed the title Bash WordSplitting Problem in Bash Dec 17, 2018
@Naetw Naetw added Bash WIP and removed WIP labels Dec 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant