2017年12月29日 星期五

Bash Shell | 如何使用參數擴展 Parameter Expansion

最基本的參數擴展(Parameter Expansion),以 $ 字號開頭,後面接參數名,例如:${parameter},大括號 ( { ) 可以加或不加,例如:$parameter,但是如果遇到參數名與其後面的字元連接,在參數擴展時會產生問題,因此要加上大括號 ( { ) 來做區隔。
除此之外,Bash 還有提供一些好用的參數擴展(Parameter Expansion)如下。

參數為 unset 表示參數還沒有被初始化,為 null 表示參數被初始化為空字串,
例如:var0=

使用 unset 指令可以將參數變成 unset

1. 參數為 unset 或 null 時的參數擴展(Parameter Expansion)

a. ${parameter:-word}
   當 parameter 為 unset 或 null 時,擴展為 word。省略冒號( : ),
   如 ${parameter-word} 則只有檢查 parameter 是否為 unset。

範例:

#!/bin/sh

echo "var0 is unset"
unset var0
echo \${var0:-value0} = ${var0:-value0}
echo ""

echo "var0 is null"
var0=
echo \${var0:-value1} = ${var0:-value1}
echo ""

echo "var0 is set to default"
var0=default
echo \${var0:-value2} = ${var0:-value2}

結果:

var0 is unset
${var0:-value0} = value0

var0 is null
${var0:-value1} = value1

var0 is set to default
${var0:-value2} = default


b. ${parameter:=word}
   當 parameter 為 unset 或 null 時,擴展為 word,且 word 被設定給  
   parameter,省略冒號( : ),如 ${parameter=word} 則只有檢查 parameter 
   是否為 unset。

範例:

#!/bin/sh

echo "var0 is unset"
unset var0
echo \${var0:=value0} = ${var0:=value0}
echo \${var0} = ${var0}
echo ""

echo "var0 is null"
var0=
echo \${var0:=value1} = ${var0:=value1}
echo \${var0} = ${var0}
echo ""

echo "var0 is set to default"
var0=default
echo \${var0:=value2} = ${var0:=value2}
echo \${var0} = ${var0}

結果:

var0 is unset
${var0:=value0} = value0
${var0} = value0

var0 is null
${var0:=value1} = value1
${var0} = value1

var0 is set to default
${var0:=value2} = default
${var0} = default

c. ${parameter:?word}
   當 parameter 為 unset 或 null 時,word 會輸出到標準錯誤輸出 (stderr),
   省略冒號( : ),如 ${parameter?word} 則只有檢查 parameter 是否為 
   unset。

範例:

#!/bin/sh

echo "var0 is unset"
unset var0
echo \${var0:?var0 is unset or null} = ${var0:?var0 is unset or null}
echo ""

結果:

var0 is unset
./expansion.sh: 5: var0: var0 is unset or null

範例:

#!/bin/sh

echo "var0 is null"
var0=
echo \${var0:?var0 is unset or null} = ${var0:?var0 is unset or null}
echo ""

結果:

var0 is null
./expansion.sh: 5: var0: var0 is unset or null

範例:

#!/bin/sh

echo "var0 is set to default"
var0=default
echo \${var0:?var0 is unset or null} = ${var0:?var0 is unset or null}

結果:

var0 is set to default
${var0:?var0 is unset or null} = default


d. ${parameter:+word}
   當 parameter 不是 unset 或 null 時,擴展為 word,省略冒號( : )
   如 ${parameter+word} 則只有檢查 parameter 是否為 unset。

範例:

#!/bin/sh

echo "var0 is unset"
unset var0
echo \${var0:+value0} = ${var0:+value0}
echo ""

echo "var0 is null"
var0=
echo \${var0:+value1} = ${var0:+value1}
echo ""

echo "var0 is set to default"
var0=default
echo \${var0:+value2} = ${var0:+value2}

結果:

var0 is unset
${var0:+value0} =

var0 is null
${var0:+value1} =

var0 is set to default
${var0:+value2} = value2

2017年12月28日 星期四

Bash Shell | 如何取得 process 資訊

/proc 是取得 process 資訊的好地方,可以查看 process 開啟了哪些 file descriptor 或是執行時帶了哪些參數,process 的 group ID 與 session ID 等等。

範例:

1. 查看 file descriptor

ls -l /proc/[pid]/fd

範例:

ls -l /proc/$$/fd

結果:

total 0
lrwx------. 1 william william 64 Dec 28 21:43 0 -> /dev/pts/0
lrwx------. 1 william william 64 Dec 28 21:43 1 -> /dev/pts/0
lrwx------. 1 william william 64 Dec 28 21:42 2 -> /dev/pts/0
lrwx------. 1 william william 64 Nov 30 20:53 255 -> /dev/pts/0


2. 執行時參數

cat /proc/[pid]/cmdline

範例:

cat /proc/$(pidof ntpd)/cmdline 

結果:

ntpd-untp:ntp-p/var/run/ntpd.pid-g

3. group ID 與 session ID

cat /proc/[pid]/stat

範例:

cat /proc/$$/stat

結果:

3779 (bash) S 3777 3779 3779 34816 14474 4202496 41588 727153 1 35 271 281 3134 1949 20 0 1 0 2350634 5517312 482 4294967295 134508544 135363948 3218920976 3218920004 11592726 0 65536 3686404 1266761467 3225663865 0 0 17 0 0 0 1 0 0

完整資訊可以執行 man 5 proc 來查看

Bash Shell | 什麼是特殊參數 Special Parameters

Bash 提供了一些好用的特殊參數 (Special Parameters),其中很常用到的如:$#, $?, $0, $$,建議要記起來。

1. $*
此參數會被擴展成位置參數 (Positional Parameters),從 $1 開始,當參數沒有被雙引號 (Double Quote) 包起來時,每個位置參數都被當成獨立的字,如果參數被雙引號包起來時,則全部的位置參數會被當成一個字看待,且每一個位置參數之間都用變數 IFS 的第一個字元來做分隔,如果 IFS 是 unset 則用 space 做分隔,如果 IFS 是 null 則位置參數之間沒有字元分隔。

範例:

執行 special_parameter.sh 並傳入四個參數 red orange yellow green 如下

./special_parameter.sh red orange yellow green

#!/bin/sh

echo "\$*"
for i in $*
do
        echo $i
done
echo ""

echo "\"\$*\" and \$IFS = space tab newline"
for i in "$*"
do
        echo $i
done
echo ""

unset IFS
echo "\"\$*\" and unset \$IFS"
for i in "$*"
do
        echo $i
done
echo ""

IFS=
echo "\"\$*\" and \$IFS = null"
for i in "$*"
do
        echo $i
done

echo ""

結果:

$*
red
orange
yellow
green

"$*" and $IFS = space tab newline
red orange yellow green

"$*" and unset $IFS
red orange yellow green

"$*" and $IFS = null
redorangeyellowgreen

2. $@
此參數跟 $* 有像,會被擴展成位置參數 (Positional Parameters),從 $1 開始,但是不管是否參數有沒有被雙引號 (Double Quote) 包起來,每個位置參數都被當成獨立的字。

範例:

執行 special_parameter.sh 並傳入四個參數 red orange yellow green 如下

./special_parameter.sh red orange yellow green

#!/bin/sh

echo "\"\$@\""
for i in "$@"
do
        echo $i
done
echo ""

echo "\$@"
for i in $@
do
        echo $i

done

結果:

"$@"
red
orange
yellow
green

$@
red
orange
yellow
green

3. $#
此參數會被擴展成位置參數 (Positional Parameters)的數量,以十進制顯示。

範例:

執行 special_parameter.sh 並傳入四個參數 red orange yellow green 如下

./special_parameter.sh red orange yellow green

#!/bin/sh

echo \$# = $#

結果:

$# = 4

4. $?
此參數存放最後一個執行之前景(foreground)指令的離開狀態 (Exit Status)

範例:

#!/bin/sh

echo 123 > file1
cmp file1 file1
echo \$? = $?

echo 456 > file2
cmp file2 file2
echo \$? = $?

結果:


$? = 0

file1 file2 differ: byte 1, line 1

$? = 1


5. $-
此參數會被擴展成目前設定為 on 之 option flags 的代表字元,透過 set 指令可以看到 option flags 的設定為何。

範例:

#!/bin/sh

set -o
echo ""
echo \$- = $-

結果:

allexport      off
braceexpand    on
emacs          off
errexit        off
errtrace        off
functrace      off
hashall        on
histexpand      off
history        off
ignoreeof      off
interactive-comments on
keyword        off
monitor        off
noclobber      off
noexec          off
noglob          off
nolog          off
notify          off
nounset        off
onecmd          off
physical        off
pipefail        off
posix          on
privileged      off
verbose        off
vi              off
xtrace          off

$- = hB

說明:

上面為 on 的 option flags 有四個如下
braceexpand
hashall
interactive-comments
posix

用 man set 可以查到

braceexpand Same as -B.
hashall Same as -h.
因此 $- = hB

但是 interactive-comments 與 posix 查不到對應的代表字元。

6. $$
此參數會被擴展成 shell 的 process ID。

範例:

#!/bin/sh

echo Enter $0

echo \$$ = $$

cat /proc/$$/cmdline
echo ""
cat /proc/$$/stat

echo \$_ = $_
echo Leave $0

結果:

Enter ./special_parameter2.sh
$$ = 13735
/bin/sh./special_parameter2.sh
13735 (special_paramet) S 3779 13735 3779 34816 13735 4202496 394 185 0 0 0 0 0 0 20 0 1 0 9952610 5222400 274 4294967295 134508544 135363948 3219852928 3219851956 12153878 0 65536 4 65538 3225663865 0 0 17 0 0 0 0 0 0
$_ = /proc/13735/stat

Leave ./special_parameter2.sh

7. $!
此參數會被擴展成最近一個在背景執行之 process 的 process ID (PID)

範例:

#!/bin/sh

sleep 3 &

echo \$$ = $$
echo \$! = $!

cat /proc/$$/stat

cat /proc/$!/stat

結果:

$$ = 18886
$! = 18887
18886 (bgpid.sh) S 3779 18886 3779 34816 18886 4202496 390 0 0 0 0 1 0 0 20 0 1 0 15692640 5222400 272 4294967295 134508544 135363948 3215265520 3215264548 7701526 0 65536 4 65538 3225663865 0 0 17 0 0 0 0 0 0
18887 (sleep) S 18886 18886 3779 34816 18886 4202496 213 0 0 0 0 0 0 0 20 0 1 0 15692642 4161536 109 4294967295 134512640 134533792 3217654416 3217654040 1864726 0 0 6 0 3225751263 0 0 17 0 0 0 0 0 0

說明:
1. $$ 是 bgpid.sh 的 PID 18886,$! 是在背景執行的 sleep 3 & 的 
   PID 18887
2. 從 /proc/$!/stat 的結果可以看出,18887 是 sleep 且 parent 是 18886

8. $0
此參數會被擴展成 script 的名字,參考上面 $$ 的範例

9. $_
此參數會被擴展成前一個命令的最後一個參數,參考上面 $$ 的範例。

2017年12月27日 星期三

Bash Shell | 什麼是位置參數 Positional Parameters

位置參數 (Positional Parameters) 經常被使用到,參數名用數字做代表如:$0 $1 $2,其對應到傳給 script 或 function 的參數,也可以用大括號 braces ( { ) 來表示如:${0} ${1}。

範例:

執行 positional_param.sh 並傳入五個參數 a b c d e 如下

./positional_param.sh a b c d e

#!/bin/sh

echo \$0 = $0
echo \$1 = $1
echo \$2 = $2
echo \$3 = $3
echo \$4 = $4
echo \$5 = $5
echo ""

showParams()
{
        echo "In showParams"
        echo \$0 = $0
        echo \$1 = $1
        echo \$2 = $2
        echo \$3 = $3
        echo \$4 = $4
        echo \$5 = $5
        echo \$6 = $6
}

showParams red orange yellow green blue purple

結果:

$0 = ./positional_param.sh
$1 = a
$2 = b
$3 = c
$4 = d
$5 = e

In showParams
$0 = ./positional_param.sh
$1 = red
$2 = orange
$3 = yellow
$4 = green
$5 = blue
$6 = purple

說明:
1. 從範例可看出,位置參數對應到傳給 positional_param.sh 的參數。
2. 而 showParams 裡的位置參數會變成傳給 showParams 的參數。
3. $0 比較特別,在 showParams 裡還是 positional_param.sh。

2017年12月26日 星期二

『Bash Shell』如何印出 IFS 變數的內容值與 word splitting 程式範例/完整說明

變數 IFS(Internal Field Separator)是 bash 在做 word splitting 時做為分隔符(delimiter)用,其預設值(default value)是 space, tab 與 newline,這三個字元每一個都可做為分隔符(delimiter)用。word splitting 是 bash 做完 expansions(parameter expansion, command substitution, arithmetic expansion)後,針對非 double quote 且有發生 expansions 的部分,進一步以變數 IFS 來做 word splitting。

範例:

#!/bin/sh

showParams()
{
        echo \$# = $#
        while [ "$1" ]
        do
                echo $1
                shift
        done

}

IFS=/
var1="1 2 3"
var2="a b c"
var3="d/e/f"

showParams "$var1" $var2 $var3

結果:

$# = 5
1 2 3
a b c
d
e
f


說明:
1. 將 IFS 設定為 '/'
2. "$var1" 因為被 double quote,所以 1 2 3 被當做一整個參數。
3. $var2 的內容值是 a b c,因為沒有包含 IFS 的 '/',所以也被當做一整個參數。
4. $var3 的內容值是 d/e/f,包含 IFS 的 '/',所以被 word splitting 成三個參數。

變數 IFS(Internal Field Separator)的預設值是 space, tab, newline,直接印也看不出所以然,但是可以透過 od 的幫助來顯示其內容值。

範例:

#!/bin/sh

echo -n "$IFS" | od -a -t x1 -c

結果:


0000000  sp  ht  nl
         20  09  0a
             \t  \n
0000003

說明:
1. -n 是為了讓 echo 不要丟出 newline。
2. "$IFS" 加上 double quote 才能讓 echo 得到 $IFS 的內容值。

如果修改過變數 IFS(Internal Field Separator)的值後,想將其設定回預設值,可以使用以下方式。

範例:

#!/bin/sh

IFS=$' \t\n'
echo -n "$IFS" | od -a -t x1 -c

結果:

0000000  sp  ht  nl
         20  09  0a
             \t  \n
0000003

2017年12月22日 星期五

Bash Shell | 如何使用 sort Command 排序版本號碼

筆者最近在編寫 shell script 做為軟體釋出 (Software Release) 自動化用,想要從 git tag 取出最後一次軟體釋出的版號 (Version) 時遇到問題,因為版號格式是 x.y.z,當欄位出現兩位數字以上時,排序出現問題。

假設 git tag 的結果如下

0.1.1
0.1.10
0.1.12
0.1.2

使用 sort 指令做排序,但是結果沒變

範例:

git tag | sort

結果:

0.1.1
0.1.10
0.1.12
0.1.2

sort 指令加上參數 -V 則結果符合需求

範例:

git tag | sort -V

結果:

0.1.1
0.1.2
0.1.10
0.1.12

2017年12月11日 星期一

Bash Shell | 如何使用管線 Pipeline


管線 Pipeline 可以將一個指令的輸出給另外一個指令當做輸入,格式如下

command1 [ | or |&  command2 ] ...

管線有兩種代表符號
1. ( | )
   將一個指令的輸出(stdout)給另一個指令當作輸入,底下是取得網路介面 IP 的範例

範例:

#!/bin/sh

echo "1. The output of ifconfig eth0:"
echo ""
ifconfig eth0
echo "---------------------------------------------------------"
echo "2. The output of ifconfig eth0 | grep \"inet addr\""
echo ""
ifconfig eth0 | grep "inet addr"
echo "---------------------------------------------------------"
echo "3. The output of ifconfig eth0 | grep \"inet addr\" | awk '{print \$2}'"
echo ""
ifconfig eth0 | grep "inet addr" | awk '{print $2}'
echo "---------------------------------------------------------"
echo "4. The output of ifconfig eth0 | grep \"inet addr\" | awk '{print \$2}' | awk -F':' '{print \$2}'"
echo ""
ifconfig eth0 | grep "inet addr" | awk '{print $2}' | awk -F':' '{print $2}'


結果:

1. The output of ifconfig eth0:

eth0      Link encap:Ethernet  HWaddr 08:00:27:08:DA:E6  
          inet addr:192.168.1.115  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:fe08:dae6/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:33355 errors:0 dropped:0 overruns:0 frame:0
          TX packets:5186 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:6077695 (5.7 MiB)  TX bytes:415741 (405.9 KiB)

---------------------------------------------------------
2. The output of ifconfig eth0 | grep "inet addr"

          inet addr:192.168.1.115  Bcast:192.168.1.255  Mask:255.255.255.0
---------------------------------------------------------
3. The output of ifconfig eth0 | grep "inet addr" | awk '{print $2}'

addr:192.168.1.115
---------------------------------------------------------
4. The output of ifconfig eth0 | grep "inet addr" | awk '{print $2}' | awk -F':' '{print $2}'

192.168.1.115

2. ( |& )
   將一個指令的輸出(stdout 與 stderr)給另一個指令當作輸入

範例:

#/bin/sh

unset var1
echo "1. The output of declare -p var1"
echo ""
declare -p var1
echo "---------------------------------------------------------"
echo "2. use |"
if declare -p var1 | grep "not found"; then
        echo "keyword is found"
else
        echo "keyword is not found"
fi
echo "---------------------------------------------------------"
echo "3. use |&"
if declare -p var1 |& grep "not found"; then
        echo "keyword is found"
else
        echo "keyword is not found"
fi


結果:

1. The output of declare -p var1

./pipelines.sh: line 24: declare: var1: not found
---------------------------------------------------------
2. use |
./pipelines.sh: line 27: declare: var1: not found
keyword is not found
---------------------------------------------------------
3. use |&
./pipelines.sh: line 34: declare: var1: not found
keyword is found

說明:

利用 declare 指令找不到參數名時會往 stderr 送出錯誤訊息的特性,範例中使用兩種不同管線 Pipelines 得到不同的結果。當使用 ( | ) 時,grep 因為沒有收到 declare 往 stderr 送出的錯誤訊息,因此結果是 keyword is not found,而使用 ( |& )時,因為 declare 的 stderr 也會一併給 grep,所以結果是 keyword is found。

2017年12月3日 星期日

『Bash Shell』如何使用內建指令 local Command 宣告區域變數 程式範例/完整說明

全域變數與區域變數的宣告有底下兩件事情要注意

1. 在 shell script 或 function 內宣告變數時,都是全域變數。

範例:

#!/bin/sh

var0="global_var0"

function local_var()
{
        declare -p var0
        var1="global_var1"
        declare -p var1
}

declare -p var0
local_var
declare -p var1

結果:

declare -- var0="global_var0"
declare -- var0="global_var0"
declare -- var1="global_var1"
declare -- var1="global_var1"

說明:

範例中使用 declare -p 來看變數是否存在,在 shell script 內宣告的 var0,在 local_var() 函式也是存在,表示 var0 是全域變數。而 var1 是在 local_var() 函式內宣告,但是離開 local_var() 函式後,var1 還是存在,表示 var1 也是全域變數。

2. 區域變數需要使用 local Command 來宣告,但只有函式 function 內可以使用 local Command 來宣告區域變數。

範例:



#!/bin/sh



function local_var()

{
        local var1="global_var1"
}

local_var
declare -p var1

local var0="global_var0"

結果:

./local_cmd2.sh: line 9: declare: var1: not found
./local_cmd2.sh: line 11: local: can only be used in a function

說明:

1. var1 是在 local_var() 函式內宣告,離開 local_var() 函式後,var1 不存在,表示 var1 是區域變數。

2. 在 shell script 內使用 local Command 宣告 var0 發生錯誤,因為 local Command 只能在函式 function 中被使用。

好站連結
1. http://linux.vbird.org/linux_basic/0320bash.php#variable_range