Shell 作為系統管理和自動化任務的核心工具,其進階指令的掌握對於提升效率至關重要。本文將探討 Shell 內建變數,如$#$-$?$$$!$n$0$*$@,並闡述它們在指令碼中的應用。同時,我們將解析 shift 和 set 命令的用法,以及如何處理互動式輸入。子 Shell 環境的建立與變數匯出是另一個重點,將詳細說明如何在不同 Shell 層級間管理變數。測試運算元的運用,涵蓋檔案、字串和數值比較,是指令碼邏輯判斷的基礎。控制結構,包含條件判斷(if-else)、案例選擇(case)和迴圈結構(while),則賦予指令碼更強大的流程控制能力。最後,我們將探討迴圈結構的應用,包含 while、until 和 for 迴圈,以及 break 和 continue 命令的用法,並介紹特殊字串操作技巧,如使用 expr 運算元計算長度和提取子字串。文章將搭配指令碼範例與流程圖示,幫助讀者更清晰地理解 Shell 指令碼的運作機制,並提供實際應用場景的分析方法論,引導讀者編寫更有效率的自動化指令碼。

進階Shell指令:變數、命令及子shell操作

Shell變數的自動設定

當Shell啟動時,會自動設定一些變數,這些變數允許我們參照命令列中的引數。以下是一些常見的Shell內建變數及其用法:

  • $#:命令列引數的數量。
  • $-:傳遞給Shell的選項。
  • $?:最後執行命令的離開值。
  • $$:當前程式的程式號。
  • $!:最後在背景中執行的命令的程式號。
  • $n:命令列引數,其中 n 從1到9,由左至右排列。
  • $0:當前Shell或程式的名稱。
  • $*:所有傳遞給Shell程式的命令列引數("$1 $2 … $9")。
  • $@:所有命令列引數,每個引數分別引號括起("$1" “$2” … “$9”)。

我們可以透過一個簡單的指令碼來說明這些變數:

echo$#:” $#
echo$#:’ $#
echo$-:’ $-
echo$?:’ $?
echo$$:’ $$
echo$!:’ $!
echo$3:’ $3
echo$0:’ $0
echo$*:’ $*
echo$@:’ $@

執行此指令碼並傳遞一些引數後,會顯示各個變數的值,例如:

$ ./variables.sh one two three four five
5: 5

$#: 5
$-:
$?: 0
$$: 12417
$!: 12418
$3: three
$0: ./variables.sh
$*: one two three four five
$@: one two three four five

指令碼解密:

這段指令碼會顯示各個內建變數的值。$# 顯示命令列引數的數量,$- 顯示傳遞給Shell的選項,$? 顯示最後執行命令的離開值,$$$! 分別顯示當前程式和最後背景執行命令的程式號。其他變數則顯示具體的命令列引數。

shift命令

Shift命令用於將位置引數向左移動一位。這在處理大於九個引數時非常有用。以下是Shift命令的一個範例:

vi shift.sh
echo “functioning of shift command”
echo $1
shift
echo $1

執行此指令碼並傳遞引數後,會顯示Shift命令的效果:

shift.sh shift command
functioning of shift command
shift
command

指令碼解密:

這段指令碼首先列印第一個引數,然後使用Shift命令將所有引數向左移動一位,再列印新的第一個引數。

set命令

Set命令用於明確初始化位置引數的值。以下是Set命令的一個範例:

vi set.sh
set ONE TWO THREE
echo $1
echo $2
echo $3

執行此指令碼後,會顯示初始化的位置引數:

set.sh

ONE TWO THREE:
l EDITOR= /users/bin HOME=/users/George LOGNAME=Geore PATH= /users/bin

指令碼解密:

這段指令碼使用Set命令將ONE、TWO和THREE分別指定給 $1$2$3,然後列印這些值。

互動輸入

Shell指令碼可以接受互動輸入來設定引數。Shell使用內建命令read來讀取標準輸入並將空白分隔的詞語分配給變數。以下是一個簡單的範例:

vi add.sh

while read v1 v2 do echo $((v1 – v2)) done

執行此指令碼並輸入一些值後,會顯示計算結果:

add.sh

23 13 10 25 5 20 Ctrl + d $

指令碼解密:

這段指令碼使用read命令和while迴圈來不斷讀取兩個輸入值並計算它們之間的差異。

子Shell

子Shell是Shell在執行指令碼時建立的一個新Shell環境。子Shell具有自己的環境變數和區域性變數,無法直接修改父Shell中的環境變數。以下是一個範例:

vi loc_var.sh echo initialize local variable x in sub shell x=50 echo $x loc_var.sh execute loc_var.sh

50 Sub shell terminated echo x $

指令碼解密:

這段指令碼在子Shell中初始化了一個區域性變數 x,但在父Shell中無法存取該變數。

Exporting變數

如果需要在子Shell中存取父Shell中的區域性變數,必須先匯出這些變數。以下是一個範例:

x=200 export x vi exp_var.sh echo x = $x exp_var.sh x=200

指令碼解密:

首先在父Shell中初始化並匯出變數 x,然後在子Shell中存取該變數。

測試運算元

Test運算元用於測試邏輯條件,可以進行數值比較、字串比較或檔案相關比較。以下是一些常見的Test運算元範例:

檔案測試運算元

每個Shell指令碼都處理檔案。Test指令和一些常見運算元包括:

  • -e filename: 檢查檔案是否存在。
  • -f filename: 檢查檔案是否為一般檔案。
  • -d filename: 檢查檔案是否為目錄。

指令碼解密:

這些測試運算元用於檢查檔案屬性,例如檔案是否存在或是否為目錄。

指令碼程式中的檢查與條件判斷

在指令碼程式中,檢查與條件判斷是非常重要的功能,因為它們允許我們根據不同的情境執行不同的操作。以下,玄貓將詳細介紹在指令碼中常見的檢查型別及其應用。

檔案檢查

檢查檔案屬性是指令碼中常見的操作。以下是一些常用的檢查運算元:

  • -r:檢查檔案是否存在且可讀。
  • -w:檢查檔案是否存在且可寫。
  • -x:檢查檔案是否存在且可執行。
  • -f:檢查檔案是否存在且為一般檔案。
  • -d:檢查檔案是否存在且為目錄。
  • -h 或 -L:檢查檔案是否存在且為符號連結。
  • -c:檢查檔案是否存在且為字元特殊檔案。
  • -b:檢查檔案是否存在且為區塊特殊檔案。
  • -p:檢查檔案是否存在且為命名管道(fifo)。
  • -u:檢查檔案是否存在且設定了setuid位元。
  • -g:檢查檔案是否存在且設定了setgid位元。
  • -k:檢查檔案是否存在且設定了sticky位元。
  • -s:檢查檔案是否存在且大小大於零。

這些運算元都是單元性質的,它們接受一個引數作為要檢查的檔案名稱,包括目錄。

實際應用範例

以下範例展示如何使用test命令來檢查檔案型別:

# 檢查檔案
echo "請輸入一個檔名稱"
read fname
if test -d "$fname"
then
    echo "$fname 是一個目錄"
else
    echo "$fname 是一個檔案"
fi

字串比較

在指令碼中,我們常需要比較字串。以下是一些常用的字串比較運算元:

  • -z string:如果字串長度為零則為真。
  • -n string:如果字串長度非零則為真。
  • string1 = string2:如果字串1等於字串2則為真。
  • string1 != string2:如果字串1不等於字串2則為真。
  • string:如果字串不是NULL則為真。

實際應用範例

以下範例展示如何使用test命令來檢查字串:

# 檢查字串是否為空
echo "請輸入一個檔名稱"
read fname
if test -z "$fname"
then
    echo "請輸入非空檔名稱"
fi

數值比較

在指令碼中,我們也常需要比較數值。以下是一些常用的數值比較運算元:

  • n1 -eq n2:如果整數n1等於n2則為真。
  • n1 -ne n2:如果整數n1不等於n2則為真。
  • n1 -gt n2:如果整數n1大於n2則為真。
  • n1 -ge n2:如果整數n1大於或等於n2則為真。
  • n1 -lt n2:如果整數n1小於n2則為真。
  • n1 -le n2:如果整數n1小於或等於n2則為真。

實際應用範例

以下範例展示如何使用數值比較運算元進行比較:

# compare.sh
echo "請輸入兩個數值"

read n1 n2
if test $n1 -eq $n2
then
    echo "這兩個數值相等"
elif test $n1 -gt $n2
then
    echo "$n1 大於 $n2"
else
    echo "$n2 小於 $n1"
fi

邏輯運算元

在條件判斷中,我們經常需要使用邏輯運算元來組合多個條件。以下是一些常用的邏輯運算元:

  • !: 負運算元(邏輯非)

    if [ ! -r /users/home/data ]
    then
        echo "目錄 /users/home/data 不可讀"
    fi
    
  • -a 或 &&: 邏輯與運算元(邏輯與)

    if [ -f "$file1" -a -r "$file1" ]
    then
        echo "檔案 $file1 是普通檔案並且可讀"
    fi
    
  • -o 或 ||: 邏輯或運算元(邏輯或)

    if [ -f "$file1" -o -r "$file1" ]
    then
        echo "檔案 $file1 是普通檔案或者可讀"
    fi
    

控制結構

在指令碼程式中,控制結構允許我們根據不同的條件執行不同的操作。以下是一些常見的控制結構:

條件判斷(if-else)

條件判斷結構允許我們根據一個或多個條件執行不同的操作。

if condition1
then
    command_list_if_condition1_is_true
elif condition2
then
    command_list_if_condition2_is_true
else
    command_list_if_condition_false
fi

案例選擇(case)

當我們需要根據某個變數的值選擇不同的操作時,可以使用case結構。

case parameter in
    pattern1) command_list_for_pattern1;;
    pattern2) command_list_for_pattern2;;
    pattern3) command_list_for_pattern3;;
    *) default_command_list;;
esac

迴圈結構(while)

while迴圈允許我們重複執行某些操作,直到條件不再成立。

while condition_do_something_until_condition_false;
do_something_until_condition_false;
done;

玄貓相信這些基礎概念和範例能幫助你更好地理解和使用指令碼程式中的條件判斷和控制結構。希望對你有所幫助!

自動化指令碼編寫與控制結構

在指令碼編寫中,控制結構是非常重要的元素,因為它們能夠幫助我們處理重複性的任務。以下將詳細介紹幾個常見的控制結構,並提供實際範例來說明它們的應用。

迴圈結構

迴圈結構能夠重複執行一組指令,直到某個條件滿足為止。以下是幾種常見的迴圈結構。

while 迴圈

while 迴圈會在條件為真時重複執行指令。以下是一個簡單的 while 迴圈範例:

#!/bin/sh
while [ $# -gt 0 ]
do
echo $1
shift
done

內容解密:

這段程式碼定義了一個 while 迴圈,該迴圈會在命令列引數的數量大於零時執行。每次執行時,它會輸出第一個引數並使用 shift 命令將引數列表向左移動一位,直到所有引數都被處理完畢。

$ ./while.sh one two three
one
two
three

until 迴圈

until 迴圈與 while 迴圈相反,它會在條件為假時重複執行指令。以下是一個 until 迴圈範例:

#!/bin/sh
until [ $# -le 0 ]
do
echo $1
shift
done

內容解密:

這段程式碼定義了一個 until 迴圈,該迴圈會在命令列引數的數量小於或等於零時終止。每次執行時,它會輸出第一個引數並使用 shift 命令將引數列表向左移動一位,直到所有引數都被處理完畢。

for 迴圈

for 迴圈可以遍歷一組字串值。以下是一個 for 迴圈範例:

#!/bin/sh
for file in *.old
do
newf=`basename $file .old`
cp $file $newf.new
done

內容解密:

這段程式碼定義了一個 for 迴圈,該迴圈會遍歷當前目錄中的所有 .old 檔案。對於每個檔案,它會使用 basename 命令移除 .old 副檔名並將其複製到新的 .new 檔案。

中斷與繼續控制

在指令碼中,有時候我們需要提前終止或跳過某些迴圈執行。

break 命令

break 命令用於立即離開當前迴圈。以下是一個 break 命令範例:

#!/bin/sh
while :
do
read -p "Enter integer number: " a
if [ $a -eq -1 ]
then
break
fi
done
echo $a

內容解密:

這段程式碼定義了一個無限 while 迴圈,該迴圈會不斷提示使用者輸入整數。當使用者輸入 -1 時,程式碼會使用 break 命令離開迴圈並輸出最後一次輸入的值。

continue 命令

continue 命令用於跳過當前迴圈的剩餘部分並進入下一次迴圈執行。以下是一個 continue 命令範例:

#!/bin/sh
for number in 1 2 3 4 5 6 7 8
do
case $number in (1|3|5|7) continue ;;
esac
echo $number
done

內容解密:

這段程式碼定義了一個 for 迴圈,該迴圈會遍歷數字列表 [1,2,3,4,5,6,7,8]。當遇到奇數時,使用 continue 命令跳過當前執行並進入下一次迴圈執行。因此只會輸出偶數:2、4、6、8。

特殊字串操作

在指令碼中,我們經常需要對字串進行各種操作,如計算長度、提取子字串等。

expr 運算元

使用 expr 運算元可以方便地對字串進行各種操作。

  • 計算字串長度:使用 expr ".*"
$ expr "abcdefghijk" : '.*'

結果:

12(表示字串長度為12)。

  • 提取子字串:使用多個點來表示要跳過的字元數量。
$ expr "abcd" : '..\(..\)'

結果:

cd(表示從第三和第四位開始提取兩個字元)。

  • 定位特定字元:使用 [^char]*char
$ expr "abcdefg" : '[^d]*d'

結果:

4(表示字元 ’d’ 在第四位)。

語法解析與特性

在指令碼中,語法解析與特性處理非常重要。以下介紹一些常見的語法解析方式。

引數替代

引數替代允許我們根據條件設定引數值。

  • ${parameter}:表示引數的值。
  • ${parameter-default}:如果引數未設定,則使用預設值。
  • ${parameter=default}:如果引數未設定,則設定為預設值並使用該值。
  • ${parameter+newval}:如果引數已設定,則使用新值。
  • ${parameter?message}:如果引數未設定,則顯示訊息。

以下是一個示例指令碼:

#!/bin/sh

param0=$0

test -n "$1" && param1=$1

test -n "$2" && param2=$2

test -n "$3" && param3=$3

echo "0: $param0"
echo "1: ${param1-1}: \c"; echo $param1;
echo "2: ${param2=2}: \c"; echo $param2;
echo "3: ${param3+3}: \c"; echo $param3;

指令碼運作流程圖示(此圖示)

@startuml
:C;
:啟動指令碼; --> :檢查引數;
:B; --> :C;
:設定引數值;
:不設定引數值;
:D; --> :輸出結果;
:E; --> :輸出結果;
@enduml
說明:

從啟動指令碼開始檢查引數存在與否來決定設定引數值還是不設定引數值後輸出結果

函式編寫

在編寫指令碼時我們經常需要重複執行一些操作。函式可以幫助我們複用程式碼。

函式定義格式如下:

fcn() { command; }

其中空格和分號都是必需的。以下是兩個函式示例:

ls() { /bin/ls -sbF "$@"; }

ll() { ls -al "$@"; }

內容解密:

這兩個函式分別重定義了ls命令和ll命令來增加一些預設選項。第一個函式增加了-sbF選項給ls命令;第二個函式則是在第一個函式基礎上增加了-al選項。

根據實際應用場景推薦深度分析方法論(此圖示)

@startuml
:B;
:F;
:G;
:H;
:需要自動化指令碼; --> :B;
:B; --> :while迴圈;
:B; --> :until迴圈;
:B; --> :for迴圈;
:C; --> :F;
:D; --> :G;
:E; --> :H;
:編寫while迴圈;
:編寫until迴圈;
:編寫for迴圈;
@enduml
說明:

針對不同問題情境選擇適合自動化指令碼功能需求之控制結構方式來進行開發或改良。

透過以上對自動化指令碼編寫與控制結構進行了詳細介紹及具體範例說明其應用情境及設計考量之方法論提供讀者理解及實務開發之指引與建議