一文教會你寫 90- 的 shell 腳本!

作者丨匠心 Java

來源丨匠心 Java(ID:code-the-world)

大家好,我是老 C,很開心和大家見面~

在公司項目的開發過程中,總會有需要編寫 shell 腳本去處理一個業務的時候,也有的小夥伴總會問我關於 shell 腳本的一些知識,在這裏博主整理了一篇文章

看完這邊文章應該就可以獨立完成大部分腳本的編寫~

shell 腳本?

在說什麼是 shell 腳本之前,先說說什麼是 shell。

shell 是外殼的意思,就是操作系統的外殼。我們可以通過 shell 命令來操作和控制操作系統,比如 Linux 中的 Shell 命令就包括 ls、cd、pwd 等等。總結來說,Shell 是一個命令解釋器,它通過接受用戶輸入的 Shell 命令來啓動、暫停、停止程序的運行或對計算機進行控制。

shell 是一個應用程序,它連接了用戶和 Linux 內核,讓用戶能夠更加高效、安全、低成本地使用 Linux 內核,這就是 Shell 的本質。

shell 本身並不是內核的一部分,它只是站在內核的基礎上編寫的一個應用程序。

那麼什麼是 shell 腳本呢?

shell 腳本就是由 Shell 命令組成的執行文件,將一些命令整合到一個文件中,進行處理業務邏輯,腳本不用編譯即可運行。它通過解釋器解釋運行,所以速度相對來說比較慢。

shell 腳本中最重要的就是對 shell 命令的使用與組合,再使用 shell 腳本支持的一些語言特性,完成想要的功能。

寫 shell 腳本需要了解什麼知識呢?

語法 - 調試技巧 - 實戰案例,本篇都有,開整~

文章有點長,可以先收藏再看哦,俗話說的好:收藏從未停止,學習從未...,也歡迎大家關注我的公衆號【匠心 Java】

註釋

“#” 開頭的就是註釋,被編譯器忽略

變量

變量類型運行 shell 時,會同時存在三種變量:

  1. 局部變量:局部變量在腳本或命令中定義,僅在當前 shell 實例中有效,其他 shell 啓動的程序不能訪問局部變量。

  2. 環境變量:所有的程序,包括 shell 啓動的程序,都能訪問環境變量,有些程序需要環境變量來保證其正常運行。必要的時候 shell 腳本也可以定義環境變量。

  3. shell 變量:shell 變量是由 shell 程序設置的特殊變量。shell 變量中有一部分是環境變量,有一部分是局部變量,這些變量保證了 shell 的正常運行

變量操作

字符串變量 1)單引號

2)雙引號

3)拼接字符串

4)獲取字符串長度

5)提取子字符串

數組

bash 只支持一維數組,不支持多維數組

參數傳遞

運算符

算數運算

數字關係運算符關係運算符只支持數字,不支持字符串,除非字符串的值是數字。下面假定變量 a 爲 10,變量 b 爲 20

字符串運算符下表列出了常用的字符串運算符,假定變量 a 爲 "abc",變量 b 爲 "efg":

布爾運算符下表列出了常用的布爾運算符,假定變量 a 爲 10,變量 b 爲 20:

邏輯運算符以下介紹 Shell 的邏輯運算符,假定變量 a 爲 10,變量 b 爲 20:

文件運算符

執行相關

命令替換命令替換與變量替換差不多,都是用來重組命令行的,先完成引號裏的命令行,然後將其結果替換出來,再重組成新的命令行。執行命令:

  1. ls /etc :反引號 (所有的 unix 系統都支持)

  2. $(ls /etc) :$+() (部分 unix 系統不支持) 多個嵌套使用時,從內向外執行

for file in \s /etc\ 或 for file in $(ls /etc) 循環中使用 dirname \$0 獲取腳本文件所在的目錄 path=$(cd dirname $0;pwd) :獲取腳本當前所在目錄,並且執行 cd 命令到達該目錄,使用 pwd 獲取路徑並賦值到 path 變量

算術運算

  1. $[ ] : 加減乘除, 不必添加空格

  2. $(()) :加減乘除等, 不必添加空格

邏輯判斷

:中括號旁邊和運算符兩邊必須添加空格 (可以使用,不推薦)

  1. [[]]:中括號旁邊和運算符兩邊必須添加空格 (字符串驗證時,推薦使用)

  2. (()) :中括號旁邊和運算符兩邊必須添加空格 (數字驗證時,推薦使用)

  3. [[]] 和 (()) 分別是 [ ] 的針對數學比較表達式和字符串表達式的加強版。

  4. 使用 [[...]] 條件判斷結構,而不是 [ ... ],能夠防止腳本中的許多邏輯錯誤。比如,&&、||、<和> 操作符能夠正常存在於[[ ]] 條件判斷結構中,但是如果出現在 [ ] 結構中的話,會報錯。比如可以直接使用 if [[ $a != 1 && $a != 2 ]], 如果不適用雙括號, 則爲 if [ $a -ne 1] && [ $a != 2 ]或者 if [ $a -ne 1 -a $a != 2 ]。[[ ]]中增加模式匹配特效;(( ))不需要再將表達式裏面的大小於符號轉義,除了可以使用標準的數學運算符外,還增加了以下符號 

輸出

echo 僅用於字符串的輸出,沒有使用 printf 作爲輸出的移植性好,建議使用 printf

printf

printf 不會像 echo 自動添加換行符,我們可以手動添加 \n 無大括號,直接以空格分隔

流程控制

和 Java、PHP 等語言不一樣,sh 的流程控制不可爲空,即 if 或者 else 的大括號中無任何語句 if else

if condition
then
    command1
    command2
    ...
    commandN
fi
if condition
then
    command1
    command2
    ...
    commandN
else
    command
fi
if condition1
then
    command1
elif condition2
then 
    command2
else
    commandN
fi

for

for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done

while

while condition
do
    command
done
while :
do
    command
done

untiluntil 循環執行一系列命令直至條件爲 true 時停止。until 循環與 while 循環在處理方式上剛好相反。

until condition
do
    command
done

caseShell case 語句爲多選擇語句。可以用 case 語句匹配一個值與一個模式,如果匹配成功,執行相匹配的命令。case 需要一個 esac(就是 case 反過來)作爲結束標記,每個 case 分支用右圓括號,用兩個分號表示 break,其中 “;;” 不是跳出循環,是不在去匹配下面的模式 case 語句格式如下:

case 值 in
  模式1)
    command1
    command2
    ...
    commandN
    ;;
  模式2)
    command1
    command2
    ...
    commandN
    ;;
esac

跳出循環

  1. break :跳出總循環

  2. continue:跳出當前循環,繼續下一次循環

定義函數

可以帶 function fun() 定義,也可以直接 fun() 定義, 不帶任何參數。

[ function ] funname()
{
    action;
    [return int;]
}
funWithParam(){
    echo "第一個參數爲 $1 !"
    echo "第二個參數爲 $2 !"
    echo "第十個參數爲 $10 !"
    echo "第十個參數爲 ${10} !"
    echo "第十一個參數爲 ${11} !"
    echo "參數總數有 $# 個!"
    echo "作爲一個字符串輸出所有參數 $* !"}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
echo $?  \# 判斷執行是否成功
abs_path() {
    SOURCE="${BASH_SOURCE[0]}"
    while [ -h "$SOURCE" ]; do
        DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
        SOURCE="$(readlink "$SOURCE")"
        [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
    done
    echo "test"
    echo "$( cd -P "$( dirname "$SOURCE" )" && pwd )"
    # 此函數的兩個echo輸出會組合成一個字符串作爲下述BIN的值
}
BIN=`abs_path` # BIN賦值函數返回值,如果沒有return,則函數中所有的echo、printf輸出組合成一個字符串傳入BIN
path=${BIN}/nodetool # 可直接使用

輸入輸出重定向

一般情況下,每個 Unix/Linux 命令運行時都會打開三個文件:

默認情況下,command > file 將 stdout 重定向到 file,command < file 將 stdin 重定向到 file。如果希望執行某個命令,但又不希望在屏幕上顯示輸出結果,那麼可以將輸出重定向到 /dev/null:

輸入重定向

  1. bash.sh < file :將腳本的輸入重定向到 file,由 file 提供參數

輸出重定向

  1. bash.sh > file :將腳本的輸出數據重定向到 file 中,覆蓋數據

  2. bash.sh >> file :將腳本的輸出數據重定向到 file 中,追加數據

  3. command >> file 2>&1 :將 stdout 和 stderr 合併後重定向到 file

讀取外部輸入

命令:read arg (腳本讀取外部輸入並賦值到變量上) 在 shell 腳本執行到上述命令時,停止腳本執行並等待外部輸入,將外部輸入賦值到 arg 變量上,繼續執行腳本

文件引用

引用其他的文件之後,可以使用其變量、函數等等,相當於將引用的文件包含進了當前文件 兩種方式:

  1. . filepath\filename

  2. source filepath\filename

顏色標識

printf  "\033[32m SUCCESS: yay \033[0m\n";
printf  "\033[33m WARNING: hmm \033[0m\n";
printf  "\033[31m ERROR: fubar \033[0m\n";

輸出結果:

長句換行

在 shell 中爲避免一個語句過長,可以使用 “\” 進行換行 使用 “\” 換行,在腳本執行過程中還是當做一行一個語句執行,不同於 enter 直接換行

注意:\ 前添加一個空格 。\ 後無空格直接換行。

 /mysql/bin/mysql \
  -h test_host  -P 000 \
  -u test_user -ptest_password ;

shell 操作 mysql

下面案例爲登錄 mysql,並選擇操作數據庫,之後進行導入數據

 /mysql/mysql/bin/mysql \
  -h test_host  -P 000 \
  -u test_user -ptest_password \
  -e"use test_database; source data_faile; " # -e 代表執行sql語句

-u 用戶名 -p 用戶密碼 -h 服務器 ip 地址 -D 連接的數據庫 -N 不輸出列信息 -B 使用 tab 鍵 代替 分隔符 -e 執行的 SQL 語句

退出腳本

命令:exit

在退出腳本時使用不同的錯誤碼,這樣可以根據錯誤碼來判斷髮生了什麼錯誤。

在絕大多數 shell 腳本中,exit 0 表示執行成功,exit 1 表示發生錯誤。對錯誤與錯誤碼進行一對一的映射,這樣有助於腳本調試。

命令:set-e或者set+eset -e 表示從當前位置開始,如果出現任何錯誤都將觸發 exit。相反,set +e 表示不管出現任何錯誤繼續執行腳本。

如果腳本是有狀態的(每個後續步驟都依賴前一個步驟),那麼請使用 set -e,在腳本出現錯誤時立即退出腳本。如果要求所有命令都要執行完(很少會這樣),那麼就使用 set +e。

shell 腳本調試

檢查是否有語法錯誤 -nbash-n script_name.sh使用下面的命令來執行並調試 Shell 腳本 -xbash-x script_name.sh調試 countoddnumber.sh 程序案例:

#!/usr/bin.env bash
# 用於計算數組中奇數的和
# @author liyangyang
# @time 2019/09/17
sum=0
for num in 1 2 3 4;do
    re=${num}%2
    if (( ${re} == 1 ));then
        sum=$[${sum}+${num}]
    fi
done
echo ${sum}
  1. 首先檢查有無語法錯誤: bash-n count_odd_number.sh

  2. 沒有輸出,說明沒有錯誤,開始實際調試: bash-x count_odd_number.sh

  3. 調試結果如下:

+ sum=0
+ for num in 1 2 3 4
+ re=1%2
+ ((  1%2 == 1  ))
+ sum=1
+ for num in 1 2 3 4
+ re=2%2
+ ((  2%2 == 1  ))
+ for num in 1 2 3 4
+ re=3%2
+ ((  3%2 == 1  ))
+ sum=4
+ for num in 1 2 3 4
+ re=4%2
+ ((  4%2 == 1  ))
+ echo 4
4

其中的輸出顯示了程序執行的每一步,通過觀察程序執行的步驟是否滿足預期從而達到調試的效果 帶有 + 表示的是 Shell 調試器的輸出,不帶 + 表示程序的輸出。

案例:

這是 es(ElasticSearch)官方啓動服務的腳本,看可不可以理解吧~

#!/usr/bin/env bash
# CONTROLLING STARTUP:
#
# This script relies on a few environment variables to determine startup
# behavior, those variables are:
#
#   ES_PATH_CONF -- Path to config directory
#   ES_JAVA_OPTS -- External Java Opts on top of the defaults set
#
# Optionally, exact memory values can be set using the `ES_JAVA_OPTS`. Note that
# the Xms and Xmx lines in the JVM options file must be commented out. Example
# values are "512m", and "10g".
#
#   ES_JAVA_OPTS="-Xms8g -Xmx8g" ./bin/elasticsearch
source "`dirname "$0"`"/elasticsearch-env
parse_jvm_options() {
  if [ -f "$1" ]; then
    echo "`grep "^-" "$1" | tr '\n' ' '`"
  fi
}
ES_JVM_OPTIONS="$ES_PATH_CONF"/jvm.options
ES_JAVA_OPTS="`parse_jvm_options "$ES_JVM_OPTIONS"` $ES_JAVA_OPTS"
# manual parsing to find out, if process should be detached
if ! echo $* | grep -E '(^-d |-d$| -d |--daemonize$|--daemonize )' > /dev/null; then
  exec \
    "$JAVA" \
    $ES_JAVA_OPTS \
    -Des.path.home="$ES_HOME" \
    -Des.path.conf="$ES_PATH_CONF" \
    -cp "$ES_CLASSPATH" \
    org.elasticsearch.bootstrap.Elasticsearch \
    "$@"
else
  exec \
    "$JAVA" \
    $ES_JAVA_OPTS \
    -Des.path.home="$ES_HOME" \
    -Des.path.conf="$ES_PATH_CONF" \
    -cp "$ES_CLASSPATH" \
    org.elasticsearch.bootstrap.Elasticsearch \
    "$@" \
    <&- &
  retval=$?
  pid=$!
  [ $retval -eq 0 ] || exit $retval
  if [ ! -z "$ES_STARTUP_SLEEP_TIME" ]; then
    sleep $ES_STARTUP_SLEEP_TIME
  fi
  if ! ps -p $pid > /dev/null ; then
    exit 1
  fi
  exit 0
fi
exit $?
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/aalW0x04WlTEkx6wTcnS9Q