shell script 筆紀

如果要查詢 shell script 的寫法,可以利用 $ man bash 指令,內容很詳細,幾乎所有的用法都可以找到。

如果要查詢單獨 shell script 指令的用法,可以利用 type 及 help 指令,例如:
$ type if
$ help if

如果要檢查寫出來的 shell script 是否有可以改善的地方,可以利用 shellcheck,例如: (可能需要先安裝 shellcheck 套件)
$ shellcheck my_test.sh

 

1、* 在 echo 中,如果沒有被引號包含,且前、後都有空格格開的話,是代表目前資料夾下的所有檔案。
2、如果是被 `(apostrophe) 所包括的字串,會被視為指令而被執行。

1、讀取使用者的輸入使用 "read"。
2、執行 script 檔的時候,會建立一個全新的 script 環境,符合檔案一開頭指定由 /bin/bash 執行該檔案的設定,所以變數內容無法傳遞過去。
3、要想把變數內容傳遞給另一個 script 環境,可以使用 export 指令。

1、用 = 來設定變數內容時,前後都不允許有空格存在。
2、要把 script 檔案指定的變數內容設定到目前的 shell 環境,可以用 source 或是 . 指令。

1、用 for, in, do, done 這四個關鍵字完成 for 迴圈。
2、in 後續可以接多個的字串,* 表示目前資料夾下的所有檔案名稱。

while 用到的關鍵字有: while, do, done。

1、[ 是一支程式,所以在 [ 的前、後,都必須要有空格格開。
2、[ 之後的 = ,前、後也必須以空格隔開。
3、[ 之後的 ] ,之前同樣必須以空格隔開,之後可以接 ; 或 換行字元。

1、if 的關鍵字為: if, then, elif, else, fi。
2、if [] 之後可以接 ; 或是接換行符號,再接 then。
3、 -n "${something}" 的雙引號必須要加,如果不加的話因為 something 沒有被指定值,所以 ${something} 為空值,所以 -n 沒有東西可以測試,會回覆 true。

1、利用 && 及 || 符號,可以取代 if, else, fi 的撰寫。
2、A && B,只有 A 的回傳值為 true,才繼續執行 B。
3、A || B,如果 A 的回傳值為 false,才繼續執行 B。

case 用到的關鍵字為:case, in, esac, * 以及用 ;; 隔開每個 case。

$0 是檔案名稱,利用 basename 這個指令可以去掉資料夾及副檔名。
$1 ~ $9 是後續所帶的 1 ~ 9 參數。
$# 為參數個數。
$@ 為列出全部的參數。
$* 同樣也是列出全部的參數。

$@ 會把每個參數當成一個字串。
而 $* 則是把全部的參數當成一個字串。

shift 的作用會移除第一個參數,把後續參數向前移,如此,在參數超過 0 個的情況下,才有辦法往下讀取。

1、$? 的值是上一個執行結果的 return 值,如果程式順利執行的話,回傳值會是 0,否則的話,會回傳非 0 值。
2、如果不要印出執行結果的話,可以使用 "> /dev/null 2>&1",將結果導到 /dev/null。

$$ 為顯示目前 script 的 PID。
$! 為最近一次在背景執行程式的 PID。

把 IFS 設定為 : 後,就會以 : 作為變數之間的區格。
如果經判斷後,變數名稱多於實際變數,後續變數名稱的值為空值。
如果經判斷後,變數名稱少於實際變數,後續的實際變數都會被歸到最後一個變數名稱的值。
IFS 的預設值為: 空格、Tab 符號、換行符號。

可以使用 :- 來調整要使用值,如果位置在 :- 之前的變數沒有被設定過,就使用位置在 :- 之後的值。

可以使用 := 來條件性給值,如果變數之前沒有被設定的話,就給值;如果已經被設定,就不給值,按照已存在的值即可。

1、被以 `...` 括起來的內容,會被當作外部指令來執行。
2、被以 $(...) 括起來的內容,同樣也會被當作外部指令來執行。

1、宣告 function 的關鍵符號為: (), {}。
2、function 的宣告必須在呼叫之前。
3、在 script 中的變數都是全域變數,可以在 function 內修改及宣告。

1、可以用 $((...)) 來作數值計算。
2、function return 的值只有一個 byte 的容量,也就是 0 ~ 255 之間。
3、可以利用 echo 作為傳遞結果的方法。

<< 緊跟一個 delimiter,從下一行開始是想要引用的文字,文字輸入完畢後,在最後的單獨一行用相同的 delimiter 關閉。

1、trap 用來偵測指定的訊號,並在偵測到的情況下,執行指定的函式。
2、在 trep 中的 ERR 訊號,指的是偵測是否有任何指令執行失敗的訊號。

1、set 會修改 shell 的執行屬性,-e 的屬性是一遇到有執行錯誤的指令時,隨即停止繼續執行,並回傳 非0 值。
2、如果 set -e 是全部檔案內容都適用的話,可以直接加在第一行,以 
#!/bin/bash -e 取代。

利用 < 將檔案的內容導向 while 迴圈的 read 指令,接著一一讀取每一行的內容。

 

 

echo 測試

script 的內容:

#! /bin/bash

# This is a comment!
echo "1Hello      World"       # This is a comment, too!
echo "2Hello World"
echo "3Hello * World"
echo 4Hello * World
echo 5Hello      World
echo "6Hello" World
echo 7Hello "     " World
echo "8Hello "*" World"
echo "8Hello "* " World"
echo "8Hello " *" World"
echo "8Hello " * " World"
echo `9hello` world
echo a`ls` world
echo 'ahello' world
echo b *

 

所得到的結果為:

1Hello      World
2Hello World
3Hello * World
4Hello def.txt echo.sh World
5Hello World
6Hello World
7Hello       World
8Hello * World
8Hello *  World
8Hello  * World
8Hello  def.txt echo.sh  World
./echo.sh: line 16: 9hello: command not found
world
adef.txt echo.sh world
ahello world
b def.txt echo.sh

 

也就是說:
1、* 在 echo 中,如果沒有被引號包含,且前、後都有空格格開的話,是代表目前資料夾下的所有檔案。
2、如果是被 `(apostrophe) 所包括的字串,會被視為指令而被執行。

 

 

echo 測試

script 的內容:

#!/bin/bash

echo "input is: $input"
input="hi there"
echo "input is: $input"

 

所得到的結果為:

input is:
input is: hi there

$ input=ABC
$ ./read.sh
input is:
input is: hi there

$ echo $input
ABC

$ export input
$ ./read.sh
input is: ABC
input is: hi there

 

也就是說:
1、讀取使用者的輸入使用 "read"。
2、執行 script 檔的時候,會建立一個全新的 script 環境,符合檔案一開頭指定由 /bin/bash 執行該檔案的設定,所以變數內容無法傳遞過去。
3、要想把變數內容傳遞給另一個 script 環境,可以使用 export 指令。


 

 

source 測試

script 的內容:

#!/bin/bash

input="hi there"

 

所得到的結果為:

$ echo $input

$ source read.sh

$ echo $input
hi there

 

也就是說:
1、用 = 來設定變數內容時,前後都不允許有空格存在。
2、要把 script 檔案指定的變數內容設定到目前的 shell 環境,可以用 source 或是 . 指令。

 

 

for 測試

script 的內容:

#! /bin/bash

for i in hello 1 * 2 goodbye
do
  echo "Looping ... i is set to $i"
done

 

所得到的結果為:

Looping ... i is set to hello
Looping ... i is set to 1
Looping ... i is set to aa
Looping ... i is set to bb
Looping ... i is set to cc
Looping ... i is set to test.sh
Looping ... i is set to 2
Looping ... i is set to goodbye

 

也就是說:
1、用 for, in, do, done 這四個關鍵字完成 for 迴圈。
2、in 後續可以接多個的字串,* 表示目前資料夾下的所有檔案名稱。

 

 

while 測試

script 的內容:

#! /bin/bash

X=0
while [ -n "$X" ]
do
  echo "Enter some text (RETURN to quit)"
  read X
  if [ -n "$X" ]; then
    echo "You said: $X"
  fi
done

 

所得到的結果為:

Enter some text (RETURN to quit)
123
You said: 123
Enter some text (RETURN to quit)

 

也就是說:
while 用到的關鍵字有: while, do, done。


 

 

[ 測試

script 的內容:

#! /bin/bash

if [ "${AAA}" = "ABC" ]
then
    echo "\${AAA} is \"ABC\""
fi

 

所得到的結果為:

$ type [
[ is a shell builtin

$ which [
/usr/bin/[

$ help [
[: [ arg... ]
    Evaluate conditional expression.

    This is a synonym for the "test" builtin, but the last argument must
    be a literal `]', to match the opening `['.

$ ./test.sh
$

 

也就是說:
1、[ 是一支程式,所以在 [ 的前、後,都必須要有空格格開。
2、[ 之後的 = ,前、後也必須以空格隔開。
3、[ 之後的 ] ,之前同樣必須以空格隔開,之後可以接 ; 或 換行字元。

 

 

if 測試

script 的內容:

#! /bin/bash

if [ -n "${something}" ]; then
    echo "something: ${something}"
elif [ -n "${something_else}" ]; then
    echo "something_else: ${something_else}"
elif [ -n "${something_eelse}" ]
then
    echo "something_else: ${something_eelse}"
else
    echo "None of the above"
fi

 

所得到的結果為:

None of the above

 

也就是說:
1、if 的關鍵字為: if, then, elif, else, fi。
2、if [] 之後可以接 ; 或是接換行符號,再接 then。
3、 -n "${something}" 的雙引號必須要加,如果不加的話因為 something 沒有被指定值,所以 ${something} 為空值,所以 -n 沒有東西可以測試,會回覆 true。

 

 

簡易 if 測試

script 的內容:

#! /bin/bash

X=3

[ $X -ne 0 ] && echo "X isn't zero" || echo "X is zero"

[ -f $X ] && echo "X is a file" || echo "X is not a file"

[ -z "$USER" ] && echo "The \$USER variable is empty" || \
        echo "The \$USER variable is set to: \"${USER}\""

[ -n "$X" ] && echo "X is of non-zero length" || \
        echo "X is of zero length"

 

所得到的結果為:

X isn't zero
X is not a file
The $USER variable is set to: "eee"
X is of non-zero length

 

也就是說:
1、利用 && 及 || 符號,可以取代 if, else, fi 的撰寫。
2、A && B,只有 A 的回傳值為 true,才繼續執行 B。
3、A || B,如果 A 的回傳值為 false,才繼續執行 B。

 

 

case 測試

script 的內容:

#! /bin/bash

echo "Please talk to me ..."
while :
do
  read INPUT_STRING
  case $INPUT_STRING in
    hello)
        echo "Hello $USER!"
        ;;
    bye | bye2)
        echo "See you soon!"
        break
        ;;
    *)
        echo "That's cool"
        ;;
  esac
done

 

所得到的結果為:

Please talk to me ...
hello
Hello eee!
hi
That's cool
bye
See you soon!

 

也就是說:
case 用到的關鍵字為:case, in, esac, * 以及用 ;; 隔開每個 case。

 

 

parameter 測試

script 的內容:

#! /bin/bash

echo "I was called with $# parameters"
echo "My full name is $0"
echo "My name is `basename $0`"
echo "My first parameter is $1"
echo "My second parameter is $2"
echo "All parameters are $@"
echo "All parameters are $*"
echo ""
echo "13th parameter is composed of \$1 and "3": $13"

 

所得到的結果為:

$ ./parameters/test.sh  "abc" "def ggg"

I was called with 2 parameters
My full name is ./parameters/test.sh
My name is test.sh
My first parameter is abc
My second parameter is def ggg
All parameters are abc def ggg
All parameters are abc def ggg

13th parameter is composed of $1 and 3: abc3

 

也就是說:
$0 是檔案名稱,利用 basename 這個指令可以去掉資料夾及副檔名。
$1 ~ $9 是後續所帶的 1 ~ 9 參數。
$# 為參數個數。
$@ 為列出全部的參數。
$* 同樣也是列出全部的參數。

 

 

$@ vs. $* 測試

script 的內容:

#! /bin/bash

echo "all parameters are: $@"
echo "\$@ in for loop"
for p in "$@"
do
    echo "${p}"
done
echo -e "\n"


echo "all parameters are: $*"
echo "\$* in for loop"
for p in "$*"
do
    echo "${p}"
done

 

所得到的結果為:

$ ./parameters/test2.sh "abc" "def  ggg"

all parameters are: abc def  ggg
$@ in for loop
abc
def  ggg

all parameters are: abc def  ggg
$* in for loop
abc def  ggg

 

也就是說:
$@ 會把每個參數當成一個字串。
而 $* 則是把全部的參數當成一個字串。

 

 

shift 測試

script 的內容:

#! /bin/bash

while [ "$#" -gt "0" ]
do
    echo "$@"
    echo -e "\$1 is $1\n"
    shift
done

 

所得到的結果為:

abc def ggg
$1 is abc

def ggg
$1 is def

ggg
$1 is ggg

 

也就是說:

shift 的作用會移除第一個參數,把後續參數向前移,如此,在參數超過 0 個的情況下,才有辦法往下讀取。

 

 

return code 測試

script 的內容:

#! /bin/bash

ls ./non-exist-file > /dev/null 2>&1
echo -e "return value is: $?\n"

ls $0 > /dev/null 2>&1
echo "return value is: $?"

 

所得到的結果為:

return value is: 2

return value is: 0

 

也就是說:
1、$? 的值是上一個執行結果的 return 值,如果程式順利執行的話,回傳值會是 0,否則的話,會回傳非 0 值。
2、如果不要印出執行結果的話,可以使用 "> /dev/null 2>&1",將結果導到 /dev/null。

 

 

PID 測試

script 的內容:

#! /bin/bash

echo "$$"

 

所得到的結果為:

$ ./pid/test.sh &
[1] 2577
2577
[1]+  Done                    ./pid/test.sh

$ echo $!
2577

 

也就是說:
$$ 為顯示目前 script 的 PID。
$! 為最近一次在背景執行程式的 PID。

 

 

Internal Field Seperator (IFS) 測試

script 的內容:

#! /bin/bash

old_IFS="$IFS"
IFS=:
echo "Please input some data separated by colons ..."
read x y z
IFS=$old_IFS
echo "x is $x, y is $y, z is $z"

 

所得到的結果為:

$ ./ifs/test.sh
Please input some data separated by colons ...
aaa bbb ccc ddd
x is aaa bbb ccc ddd, y is , z is

$ ./ifs/test.sh
Please input some data separated by colons ...
aaa:bbb:ccc:ddd
x is aaa, y is bbb, z is ccc:ddd

 

也就是說:
把 IFS 設定為 : 後,就會以 : 作為變數之間的區格。
如果經判斷後,變數名稱多於實際變數,後續變數名稱的值為空值。
如果經判斷後,變數名稱少於實際變數,後續的實際變數都會被歸到最後一個變數名稱的值。
IFS 的預設值為: 空格、Tab 符號、換行符號。

 

 

:- 測試

script 的內容:

#! /bin/bash

echo -n "What is your name [ `whoami` ] "
read myname
echo "Your name is : ${myname:-`whoami`}"

echo "myname: ${myname}"

 

所得到的結果為:

$ ./assign/test.sh
What is your name [ eee ]
Your name is : eee
myname:

$ ./assign/test.sh
What is your name [ eee ] abc
Your name is : abc
myname: abc

 

也就是說:
可以使用 :- 來調整要使用值,如果位置在 :- 之前的變數沒有被設定過,就使用位置在 :- 之後的值。

 

 

:= 測試

script 的內容:

#! /bin/bash

echo -n "What is your name [ `whoami` ] "
read myname

myname=${myname:="default_myname"}
echo "myname: ${myname}"

 

所得到的結果為:

$ ./assign/test2.sh
What is your name [ eee ]
myname: default_myname

$ ./assign/test2.sh
What is your name [ eee ] abc
myname: abc

 

也就是說:
可以使用 := 來條件性給值,如果變數之前沒有被設定的話,就給值;如果已經被設定,就不給值,按照已存在的值即可。

 

 

執行外部程式測試

script 的內容:

#! /bin/bash

SCRIPT_FILES=`find ./ -name "*.sh" -print`
echo $SCRIPT_FILES


SCRIPT_FILES=$(find ./ -name "*.sh" -print)
echo $SCRIPT_FILES

 

所得到的結果為:

./cat_printf.sh ./pid/test.sh

./cat_printf.sh ./pid/test.sh

 

也就是說:
1、被以 `...` 括起來的內容,會被當作外部指令來執行。
2、被以 $(...) 括起來的內容,同樣也會被當作外部指令來執行。

 

 

function 測試

script 的內容:

#! /bin/bash

call_function()
{
  echo "parameter 1 and 2 are: ${1} and ${2}"
  xx=ddd
  yy=aaa
}

xx=aaa

call_function bob letmein Bob

echo "xx: ${xx}, yy: ${yy}"

 

所得到的結果為:

parameter 1 and 2 are: bob and letmein
xx: ddd, yy: aaa

 

也就是說:
1、宣告 function 的關鍵符號為: (), {}。
2、function 的宣告必須在呼叫之前。
3、在 script 中的變數都是全域變數,可以在 function 內修改及宣告。

 

 

function return 測試

script 的內容:

#! /bin/bash

square_by_return() {
    return $((${1} * ${1}))
}

square() {
    echo $((${1} * ${1}))
}

square_by_return ${1}
echo "return answer = $?"

answer=$(square ${1})
echo "echo answer = ${answer}"

 

所得到的結果為:

./function/test2.sh 18
return answer = 68
echo answer = 324

 

也就是說:
1、可以用 $((...)) 來作數值計算。
2、function return 的值只有一個 byte 的容量,也就是 0 ~ 255 之間。
3、可以利用 echo 作為傳遞結果的方法。

 

 

<< 測試

script 的內容:

#!/bin/bash

cat << END_TEXT | xargs echo
abc
def
ghi
jkl
END_TEXT

 

所得到的結果為:

abc def ghi jkl

 

也就是說:
<< 緊跟一個 delimiter,從下一行開始是想要引用的文字,文字輸入完畢後,在最後的單獨一行用相同的 delimiter 關閉。

 

 

trap 測試

script 的內容:

#!/bin/bash

trap finish ERR
finish() {
    echo "    ERR* message is trapped, exited"
    exit 1
}

cd ./abc
echo "current dir:" `pwd`

 

所得到的結果為:

./test.sh: line 10: cd: ./abc: No such file or directory
    ERR* message is trapped, exited

 

也就是說:
1、trap 用來偵測指定的訊號,並在偵測到的情況下,執行指定的函式。
2、在 trep 中的 ERR 訊號,指的是偵測是否有任何指令執行失敗的訊號。

 

 

set 測試

script 的內容:

#!/bin/bash

set -e

cd ./abc
echo "current dir:" `pwd`

 

所得到的結果為:

./test.sh: line 5: cd: ./abc: No such file or directory

$ echo $?
1

也就是說:
1、set 會修改 shell 的執行屬性,-e 的屬性是一遇到有執行錯誤的指令時,隨即停止繼續執行,並回傳 非0 值。
2、如果 set -e 是全部檔案內容都適用的話,可以直接加在第一行,以 
#!/bin/bash -e 取代。

 

 

< 測試

script 的內容:

#!/bin/bash

while read -r line ; do
    echo "${line}"
done < test.txt

 

所得到的結果為:

aa a
bb  bb
ee    ee ee

 

也就是說:

利用 < 將檔案的內容導向 while 迴圈的 read 指令,接著一一讀取每一行的內容。

 

 

 

Reference

Shell Scripting Tutorial

 

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 silverwind1982 的頭像
    silverwind1982

    拾人牙慧

    silverwind1982 發表在 痞客邦 留言(0) 人氣()