Shell擴展(Shell Expansions)-參數擴展(Shell Parameter Expansion)

從一個腳本開始

在php-docker中能夠發現一個docker-php-entrypoint.sh腳本,內容以下php

#!/bin/sh
set -e

# first arg is `-f` or `--some-option`
if [ "${1#-}" != "$1" ]; then
	set -- php-fpm "$@"
fi

exec "$@"
複製代碼

簡單解釋一下這個腳本涉及到的幾個知識點,並引出本文要介紹的Shell Parameter Expansion概念html

特殊變量$@

$@ 屬於shell腳本中幾個特殊參數中的一個,表明了傳遞給腳本的全部參數,同時還有其餘一些特殊變量能夠參考文檔Special Parameterslinux

我這裏列舉以下docker

變量 含義
$0 當前腳本的文件名
$n 傳遞給腳本或函數的參數。n 是一個數字,表示第幾個參數。例如,第一個參數是$1,第二個參數是$2。
$# 傳遞給腳本或函數的參數個數。
$* 傳遞給腳本或函數的全部參數:"$1", "$2", "$3", 每一個變量是獨立的。
$@ 傳遞給腳本或函數的全部參數:"$1 $2 $3",表明
$? 上個命令的退出狀態,或函數的返回值。
$$ 當前Shell進程ID。對於 Shell 腳本,就是這些腳本所在的進程ID。

注:$*$@ 的區別

$*$@ 都表示傳遞給函數或腳本的全部參數,不被雙引號(" ")包含時,都以"$1" "2" … "n" 的形式輸出全部參數。shell

可是當它們被雙引號(" ")包含時express

  • "$*" 會將全部的參數做爲一個總體,以"$1 2 …n"的形式輸出全部參數;
  • "$@" 會將各個參數分開,以"$1" "2" … "n" 的形式輸出全部參數。

shell,exec與source

sh

使用$ sh script.sh執行腳本時,當前shell是父進程,生成一個子shell進程,在子shell中執行腳本。腳本執行完畢,退出子shell,回到當前shell。編程

$ ./script.sh$ sh script.sh等效。數組

source

使用$ source script.sh方式,在當前上下文中執行腳本,不會生成新的進程。腳本執行完畢,回到當前shell。安全

$ . script.sh$ source script.sh等效。bash

exec方式

使用exec command方式,會用command進程替換當前shell進程,而且保持PID不變。執行完畢,直接退出,不回到以前的shell環境。

set

set 屬於shell內置命令,參考文檔#Modifying-Shell-Behavior

當單獨執行set命令時會顯示全部的環境變量和 Shell 函數

直接使用set+prams 能夠爲當前環境設置參數,例如

$ set a b c
$ echo $1
a
$ echo $2
b
$ echo $3
c
複製代碼

set -e

在"set -e"以後出現的代碼,一旦出現了返回值非零,整個腳本就會當即退出,用於保證代碼安全性

set --

其實--是一個單獨的shell特性,和set無關,它表明了一個命令的選項(options)已經結束,後面的都已是這個命令的參數了,例如:

grep -- -v file
複製代碼

若是你想搜索file中的字符串'-v',直接grep '-v' file或是其餘方法都是致使-v被識別爲grep的選項,當加入--表明選項已經沒有了,-v被理解爲第一個參數,file被理解爲第二個參數,因而就能夠在file搜索'-v'了

對於set -- 也是同樣--標誌着set的選項已經結束,後面的都是set的參數了。

爲何要這麼寫呢?很明顯是爲了防止set後面設置的變量裏含有-致使被解釋爲set自身的選項,如set -- -e這種狀況

結論

因此最開始的set -- php-fpm "$@"就能夠解釋爲:把當前環境的參數設置成 php-fpm $@$@ = php-fpm $@

對於那句if [ "${1#-}" != "$1" ]咱們在下面展開講解,根據註釋咱們能夠知道它判斷的是:傳入這個腳本的第一個參數是否是-f or --some-option這種類型

因此總結一下:

當咱們sh docker-php-entrypoint.sh -F即直接在腳本後面使用-加參數時,實際執行的是php-fpm -F

當咱們sh docker-php-entrypoint.sh ls -a即直接在腳本後面直接執行命令時,實際執行的就是傳入的命令ls -a

下面進入本文的主題

參數擴展(Shell Parameter Expansion)

在shell中可使用花括號${}包裹參數來防止緊跟在參數後面的字符串被看成參數變量名的一部分,因此最基本的參數展開就是${parameter}

間接參數擴展${!parameter}

其中引用的參數並非parameter而是parameter的實際的值

parameter="var"
var="hello"
echo ${!parameter}

hello
複製代碼

空參數處理

下面的幾種形式如${parameter:-word}是判斷parameter爲unset或者parameter=NULL來執行後續的擴展操做,即(!isSet(parameter) || parameter==NULL)

當忽略冒號後的結果${parameter:-} ,判斷parameter存在不爲NULL,即(isSet(parameter) && parameter != NULL)

當忽略冒號${parameter-word}則只判斷parameter是否存在,也就是parameter能夠爲NULL,即isSet(parameter)

  • ${parameter:-word}

當parameter未設置或者爲空則替換成word

set a b 

echo ${3:-word}   # word
echo ${1:-word}   # a

echo ${par:-word} # word
par=c
echo ${par:-word} # c
複製代碼
  • ${parameter:=word}

同上。也就是給parameter一個默認參數,因此位置參數和特殊參數不能以這種方式分配。即不能${3:=world}

set a b 

echo ${3:=word}   # -bash: $3: cannot assign in this way
echo ${1:=word}   # a

echo ${par:=word} # word
par=c
echo ${par:=word} # c
複製代碼
  • ${parameter:?word}

當變量 parameter 未設置或爲空,shell 也是可交互時,進行報錯而且退出。若是 shell 不可交互,則發生變量替換。

set a b 

echo ${3:?word}   # -bash: 3: word
echo $?           # 1 說明錯誤
echo ${1:?word}   # a

echo ${par:?word} # -bash: par: word
par=c
echo ${par:?word} # c
複製代碼
  • ${parameter:+word}

若是 parameter 爲空或未設置,那麼就什麼都不作。否則使用 word 進行替換。

set a b 

echo ${3:+word}   # 空
echo ${1:+word}   # word

echo ${par:+word} # 空
par=c
echo ${par:+word} # word
複製代碼

變量切片

  • ${parameter:offset}
  • ${parameter:offset:length}

和大部分編程語言字符串切片同樣,offset表明偏移值,length表明字符長度,須要注意的有如下幾點

  1. 起始位置爲0,截取的字符串包含第一個
  2. 當不指定length時,會截取到字符串結尾
string=01234567890abcdefgh
echo ${string: 1}           # 1234567890abcdefgh
echo ${string: 1: 2}        # 12
複製代碼
  1. 當offset小於0時,表明從尾部開始計算偏移值,開始值爲-1,同時冒號和offset之間至少有一個空格避免歧義
string=01234567890abcdefgh
echo ${string: -2}         # gh
echo ${string:-2}          # 01234567890abcdefgh 沒有空格被解釋成了:-
echo ${string: -3:2}       # fg
複製代碼
  1. 當length小於0時,表明的從尾部開始計算的偏移值而不是字符長度,因此表達式拓展爲offset到length直接的字符,注意此時是不包括length這個字符的。
string=01234567890abcdefgh
echo ${string: -7:-2}      # bcdef
複製代碼
  1. 若是參數爲@,結果是位置參數的偏移值加長度,注意此時起始計數爲1(注意和上面區分),若是offset爲負值,則表明從尾部計數,length不能爲負數
string=01234567890abcdefgh
set -- 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
echo ${string:7}          # 890abcdefgh
echo ${@:7}               # 7 8 9 0 a b c d e f g h 注意和上面的區別

echo ${@: 0}              # 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
echo ${@: 1}              # 1 2 3 4 5 6 7 8 9 0 a b c d e f g h 和上面結果同樣

echo ${string: -7}        # bcdefgh
echo ${@: -7}             # b c d e f g h

echo ${@:7:-2}            # bash: -2: substring expression < 0
複製代碼
  1. 若是參數爲下標是@或者*的數組,表達式被擴展爲從${parameter[offset]}日後length的長度的數組。負數的offset表明從尾部開始計數。length不能爲負數
array=(0 1 2 3 4 5 6 7 8 9 0 a b c d e f g h)
echo ${array[@]:7}        # 7 8 9 0 a b c d e f g h
echo ${array[@]:7:2}      # 7 8

echo ${array[@]: -7:2}    # b c
echo ${array[@]: -7:-2}   # bash: -2: substring expression < 0
複製代碼

總結:

除了位置參數是按1開始從頭計算偏移值,其餘都是按0開始計算偏移值的,從尾部都是按-1開始計算偏移值的

參數查找

  • ${!prefix*}
  • ${!prefix@}
  1. shell將其展爲全部以prefix開頭爲的變量。須要注意的是,當表達式被雙引號包裹時,@會擴展成獨立的幾個變量,而*則會擴展成變量組合而成的字符串
var1=abc
var2=def

$ for v in ${!var@};do echo $v;done; 
var1
var2

$ for v in ${!var*};do echo $v;done;
var1
var2

# 以上兩種狀況沒有區別

for v in "${!var*}";do echo $v;done;
var1 var2

for v in "${!var@}";do echo $v;done;
var1
var2

# @的被拓展成了兩個變量
複製代碼
  • ${!name[@]}
  • ${!name[*]}
  1. 若是name是一個數組,表達式會被擴展爲數組的index(鍵)
arr=(a b c d e f g h)
echo ${!arr[@]}      # 0 1 2 3 4 5 6 7
複製代碼
  1. 若是name不是數組,當name已經被設置的時候,表達式被拓展爲0,未被設置時被拓展爲null
echo ${!string[*]}  # 爲空

string=01234567890abcdefgh
echo ${!string[@]}  # 0
複製代碼
  1. 在被雙引號包裹的時候,@的表現和上面同樣。會將變量拓展成獨立的幾個變量
$ arr=(a b c d e f g h)

$ for i in  "${!arr[*]}";do echo $i; done;
0 1 2 3 4 5 6 7

$ for i in  "${!arr[@]}";do echo $i; done;
0
1
2
3
4
5
6
7

複製代碼

計算字符串的長度

  • ${#parameter}

若是parameter是字符串,表達式擴展爲字符串的長度

若是parameter是*或者@,表達式擴展爲參數的個數

若是parameter是一個數組名,而且下標爲*或者@,表達式擴展爲數組的元素個數

set a b
echo ${#@}   # 2

echo ${#1}   # 1

par=c
echo ${#par} # 1

arr=(1 2 3)
echo ${#arr[@]} # 3
複製代碼

參數刪除

  • ${parameter#word}
  • ${parameter##word}

若變量內容從開始的數據符合「關鍵字」,則將符合的最短(使用#)或者最長(使用##)數據刪除

  1. 其中word是可使用sh中的模式匹配,如*?
set -- ab-cd-ef ef-gh-ij

echo ${1#-}     # ab-cd-ef 只會從頭部開始匹配,開頭沒有-因此就不會匹配上

echo ${1#*-}    # cd-ef
echo ${1##*-}   # ef ##會匹配最長的數據
複製代碼
  1. 若是parameter是@或者*,則會對位置參數的每個進行配合和刪除操做,最後的結果是改變以後的參數列表
set -- ab-cd-ef ef-gh-ij

echo ${@#*-}   # cd-ef gh-ij
echo ${@##*-}  # ef ij
複製代碼
  1. 若是parameter是一個數組名,而且下標爲*或者@,則匹配和刪除的操做會應用到每個數組元素中,最後的結果是改變以後的數組列表
arr=(--a --b --c)
echo ${arr[@]#-}    # -a -b -c
複製代碼
  • ${parameter%word}
  • ${parameter%%word}

總體邏輯和上面的${parameter#word}${parameter##word}差異不大,只是這二者是從開始匹配關鍵字

set -- abcd-- efgh--
echo ${1%-}         # abcd-
echo ${1%%-}        # abcd-
echo ${1%-?}        # abcd 

echo ${@%-}         # abcd- efgh-

arr=(a-- b-- c--)
echo ${arr[@]%-}    # a- b- c-
複製代碼

字符移除能夠實現一些很常見的操做:

  1. 獲取文件名,文件後綴
FILENAME=linux_bash.sh
echo ${FILENAME%.*}    # linux_bash
echo ${FILENAME##*.}   # sh
複製代碼
  1. 獲取文件路徑,或者獲取文件名
FILENAME=/home/somebody/linux_bash.sh

echo ${FILENAME##*/}   # linux_bash.sh
echo ${FILENAME%/*}    # /home/somebody
複製代碼
  1. 如上面介紹的docker-php-entrypoint.sh,判斷某字符串是否以某字符開頭
$ OPT='-option'

$ if [ ${OPT#-} != ${OPT} ];
> then
>     echo "start with -"
> else
>     echo "not start with -"
> fi

start with -
複製代碼

參數替換

  • ${parameter/pattern/string}

將parameter中出現的第一個pattern替換爲string。

其中:

  1. pattern可使用*?[]等通配符
string=abceddabceddabcedd

echo ${string/d?a/f}  # abcefbceddabcedd 只替換了第一個dda
echo ${string/d*a/f}  # abcefbcedd 替換了ddabcedda
複製代碼
  1. 若是pattern使用/開頭, 將會用string替換全部符合的匹配項
string=abceddabceddabcedd

echo ${string//d?a/f}  # abcefbcefbcedd 第二個dda也被替換成了f
複製代碼
  1. 若是pattern使用#開頭, 將會用string替換開頭的匹配項,這個就是默認的表現

  2. 若是pattern使用%開頭, 將會用sting替換結尾處的一個匹配項

string=abceddabceddabcedd

echo ${string/%d?a/f}  # abceddabceddabcedd 注意此時並未匹配到最後一個dda,緣由未知

echo ${string/%dd/f}   # abceddabceddabcef 替換了尾部的dd
複製代碼
  1. 若是string是空,那麼匹配項會被刪除,並忽略pattern後面的/
string=abceddabceddabcedd
echo ${string/dd/}    # abceabceddabcedd 刪除了第一個dd
複製代碼
  1. 若是經過Shopt Builtin開啓了大小寫不敏感,那麼則能夠按照忽略大小寫來匹配

  2. 若是參數是@或者*,將會對每個位置參數進行匹配替換操做

set -- abc abd abe
echo ${@/a/f}          # fbc fbd fbe
複製代碼
  1. 若是參數是數組且以@或者*做爲參數,則對數組每個元素進行匹配替換操做
arr=(abc abd abe)
echo ${arr[@]/a/f}     # fbc fbd fbe
複製代碼

大小寫修改

  • ${parameter^pattern}
  • ${parameter^^pattern}
  • ${parameter,pattern}
  • ${parameter,,pattern}

注:此操做僅適用於bash4.0往上版本

這些拓展用於修改字符串中的大小寫。pattern表示的是匹配的模式,對於能匹配項會進行大小寫轉換(此處表示不理解)

  1. 小寫轉大寫: ^會把開頭的小寫字母轉換成大寫,^^會轉換全部小寫成大寫
par='abc'

echo ${par^}   # Abc
echo ${par^^}  # ABC
複製代碼
  1. 大寫轉小寫: ,會把開頭的大寫轉換成小寫,,,會把因此大寫轉換成小寫
par='ABC'

echo ${par,}   # aBC
echo ${par,,}  # abc
複製代碼
  1. 參數是@或者*,會對每個位置參數進行轉換
set -- ABC DEF HIJ
echo ${@,}          # aBC dEF hIJ
echo ${@,,}         # abc def hij
複製代碼
  1. 對於下標是@或者*的數組,大小寫轉換會應用於每個數組元素
arr=(ABC DEF HIJ)

echo ${arr[@],}     # aBC dEF hIJ
echo ${arr[@],,}    # abc def hij
複製代碼

變量操做

  • ${parameter@operator}

注:此操做僅適用於bash4.0往上版本

此拓展根據操做符(operator)執行參數轉換或者,操做符以下

  • Q

將字符串使用引號包裹

par='abc def'
echo ${par@Q}   # 'abc def'
複製代碼
  • E

對於使用反斜線\後的字符一概按轉義處理

# 對於雙引號包裹的字符串
par="abc\"u"

echo ${par}    # abc"u 按照轉義解釋
echo ${par@E}  # abc"u 按照轉義解釋

# 對於雙引號包裹的字符串
par="abc\'u"

echo ${par}   # abc\'u 此時單引號不須要轉義,因此展現了\
echo ${par@E} # abc'u E操做,繼續按照轉義來解釋了\

複製代碼
  • P

若是parameter含有prompt string時,按照prompt解釋(默認按照字符串解釋)

par="\@-abcd-\u"

echo ${par}    # \@-abcd-\u
echo ${par@P}  # 05:09 AM-abcd-I have no name!
複製代碼
  • A

拓展成參數賦值的語句

a=2
par=$a+1

echo ${par}   # 2+1
echo ${par@A} # par='2+1' 此時$a已經被解釋成實際值了
複製代碼
  • a

由參數屬性值組成的字符串

  • 對於@*,此操做會對每個位置參數進行處理

  • 對於下標爲@*的數組,此操做會對每個數組元素進行處理

參考:

相關文章
相關標籤/搜索