Linux-包教包會系列之-shell

前言

還記得當年被 bat 支配的恐懼。比起 shell 腳本寫 bat 腳本真的很費勁。

既然你能搞明白 java js 等這些高級語言,弄明白 shell 也是很簡單的。學會簡單的語法,再看看 tomcat 和 nacos 等你熟悉的應用中的腳本,學學別人的技巧,差不多就入門了,對於開發來說,足夠用了。

主要內容:

  • 常用語法
  • 運算符
  • 特殊變量
  • for,while,case,select 等

在線運行 shell,為了效率還是自己整個虛擬機吧。

基本語法

解析器

編寫腳本的時候,可以使用 vs code ,安裝相應的插件shell-format,可以進行語法提示和格式化。

寫腳本的時候一定要定義腳本的解析器,不然會出現怪問題。最好給系統內部的解析器一樣。

我的系統使用的 bash 解析,我寫的定義了 #!/bin/sh 解析腳本。

我調用 openssl 算法來計算路徑的 md5 怎麼都不正確。最後發現是解析器定義的不一樣。

<code>#!/bin/sh/<code>
<code>#!/bin/bash/<code>

sudo cat /etc/shells 可以查看系統的解析器。

運行 echo ${SHELL} 可以查看系統默認解析器。

我的系統是 Centos 默認 bash 解析。

<code># 打印出來 /bin/bash
echo ${SHELL}/<code>

註釋

使用 # 來註釋一行內容。

echo

echo 常用做打印一段話到顯示器。我們可以通過重定向,將內容保存到文件中去。

<code>MY_CONTENT="12124" \\
echo "${MY_CONTENT}打印內容到文件" > a.txt/<code>

當我們在控制檯輸入多行命令的時候,可以使用 \\ 來鏈接命令。

變量的引用使用 $MY_CONTENT 和 ${MY_CONTENT} 是相同的。我習慣與用後者,安全,避免 $MY_CONTENTaaa 這樣的錯誤,我寫成${MY_CONTENT}aaa 就不會有問題。

單引號與雙引號區別

<code>MY_CONTENT="12124" \\
echo "打印內容為:${MY_CONTENT}"


# 打印內容為:12124/<code>
<code>MY_CONTENT="12124" \\
echo '打印內容為:${MY_CONTENT}'

# 打印內容為:${MY_CONTENT}/<code>

單引號不會進行變量的替換。

定義變量

定義變量很簡單,直接符合 java 和 js 的變量規則就行了。通常不指定類型的話,定義都是字符串類型的數據,儘管沒有單雙引號。我習慣用常量的方式定義變量。

<code>A=111/<code>

有的時候我們需要變量的引用。定義了變量 A ,在 A 中引用 B。

<code>#!/bin/bash
BB="張攀欽"
AA="${BB}-mflyyou"
echo "${AA}-456"/<code>

有的時候還想將命令的運行結果賦值給變量。

比如,我想用 pwd 獲取當前路徑再賦值 A。

<code>#!/bin/bash

# 或者 BASE_DIR=`pwd`
BASE_DIR=$(pwd)
echo "當前路徑為:${BASE_DIR}"
/<code>

還有一些變量我們想讓其在子進程中訪問到。比如我在 a 腳本中定義的 BASE_DIR,而我在 a 中運行 b 腳本,b 腳本也可以訪問到 BASE_DIR ;

<code>export BASE_DIR=`cd $(dirname $0)/..; pwd`/<code>

特殊變量

特殊變量是已經有特殊意義的變量,可以讓我們可以獲取一些參數。

變量 描述 例子 $0 當前腳本名稱 $n 傳給腳本的參數,$1 表示第一個參數 $$ 當前 shell 的進程 id $? 上個命令的退出狀態或函數返回值,0 表示正常,其餘值異常。建議大於 0 表示異常 $# 傳給腳本或函數的參數個數

error.log 中的內容如下。

<code>#!/bin/bash

echo "當前腳本名稱 \\$0: $0"

echo "傳遞給腳本的第一個參數 \\$1: $1"

echo "當前 shell 的 pid \\$\\$: $$"

echo "上個命令執行的返回值 \\$?:$?"

echo "傳遞給腳本的個數 \\$#:$#"

# 睡眠 6 秒
sleep 6/<code>
<code>[parallels@centos-7 ~]$ sh error.log canshu1 

當前腳本名稱 $0: error.log
傳遞給腳本的第一個參數 $1: canshu1
當前 shell 的 pid $$: 14952
上個命令執行的返回值 $?:0
傳遞給腳本的個數 $#:1
[parallels@centos-7 ~]$/<code>

運算符

文件比較運算符

操作符 描述 -e 判斷是否文件存在,存在返回 true。 -f 判斷文件是否是一個普通文件,文件不存在返回 false。文件存在返回 true。 -d 判斷路徑是否是一個目錄,不存在返回 false。是目錄且存在返回 true。 -r 文件是否可讀 (指運行這個測試命令的用戶的讀權限) -w 文件是否可寫 (指運行這個測試命令的用戶的寫權限) -x 文件是否可執行 (指運行這個測試命令的用戶的執行權限)

<code># ; 與 then 之間需要有空格
if [ ! -f "${BASE_DIR}/logs/start.out" ]; then
echo "文件存在"
else
echo "文件不存在"
fi/<code>

布爾運算符

假定變量 a 為 10,變量 b 為 20:

運算符 說明 舉例 ! 非運算,表達式為 true 則返回 false,否則返回 true。 [ ! false ]


返回 true。 -o 或運算,有一個表達式為 true 則返回 true。 [ $a -lt 20 -o $b -gt 100 ]
返回 true。 -a 與運算,兩個表達式都為 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ]
返回 false。

邏輯運算符

以下介紹 Shell 的邏輯運算符,[[]] 使用。假定變量 a 為 10,變量 b 為 20:

運算符 說明 舉例 && 邏輯的 AND [[ $a -lt 100 && $b -gt 100 ]]
返回 false || 邏輯的 OR [[ $a -lt 100 || $b -gt 100 ]]
返回 true

<code>#!/bin/bash
a=10
b=20

if [[ $a -lt 100 && $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi

if [[ $a -lt 100 || $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi/<code>
<code>返回 false
返回 true/<code>

字符串比較運算符

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

運算符 說明 舉例 = 檢測兩個字符串是否相等,相等返回 true。 [ ${a} = ${b} ]


返回 false。 != 檢測兩個字符串是否相等,不相等返回 true。 [ ${a} != ${b} ]
返回 true。 < 小於,依照ASCII字符排列順序,注意"if [ “${a}" \\< “${b}" ] > 大於,依照ASCII字符排列順序,注意">"字符在[ ] 結構裡需要轉義. if [[ “${a}" > “​${b}" ]]
if [ “​${a}" > “${b}" ] -z 檢測字符串長度是否為0,為0返回 true。 [ -z “${a}“ ]
返回 false。 -n 檢測字符串長度是否為0,不為0返回 true。 [ -n "$a" ]
返回 true。

<code>#!/bin/bash
a=e1
b=e2
# 比較的時候加上一個字符混合,更安全
if [ "${a}x" == "${b}x" ]; then
echo "${a} == ${b}: a 等於 b"
else
echo "${a} == ${b}: a 不等於 b"
fi

if [ "${a}" \\> "${b}" ]; then
echo '大於'
else
echo "小於"
fi

if [[ "${a}" > "${b}" ]]; then
echo '大於'
else
echo "小於"
fi/<code>

數字比較運算符

下面這些比較符只能比較 數字 或者 數字字符串,比較非數字會報錯。

比較操作符 描述 例子 -eq 等於 if [ 3 -eq "3" ] 為 true -ne 不等於 if [ "$a" -ne "$b" ] -gt 大於 if [ "$a" -gt "$b" ] -ge 大於等於 if [ "$a" -ge "$b" ] -lt 小於 if [ "$a" -lt "$b" ] -le 小於等於 if [ "$a" -le "$b" ]

算術運算符

$(()) 可以用於數字運算。

運算操作符 描述 例子 + 加號 echo $((2+2)) - 減號 / 除號 * 乘號 ** 求冪 % 求模

定義函數

變量 描述 例子 $0 當前腳本名稱 $n 傳給腳本的參數,$1 表示第一個參數 $$ 當前 shell 的進程 id $? 上個命令的退出狀態或函數返回值,0 表示正常,其餘值異常。建議大於 0 表示異常 $# 傳給腳本或函數的參數個數

<code>f2() {
# 聲明局部變量
local loc_val=23
echo "傳給函數的第一個值為: $1"
return "22"
}
f2 aaa
# 將上個命令 f2 aaa 的返回結果拿到賦值給 code
code=$(($?))
echo "執行 f2 的返回值為 ${code}"/<code>

if

<code># ; 與 then 之間需要有空格
if [ ! -f "${BASE_DIR}/logs/start.out" ]; then
echo "文件存在"
else
echo "文件不存在"
fi/<code>

for

語法比較簡單沒有什麼可說的。

<code>strs="Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto"
for planet in ${strs}; do
echo ${planet} # 每個行星被單獨打印在一行上.
done/<code>

while

語法比較簡單沒有什麼可說的。

<code>#!/bin/bash
count=1
while [ ${count} -le 5 ];
do
echo "Loop # ${count}"
# count 自增
count=$(( ${count} + 1 ))
# ((count++))
done/<code>

case

類似 java 中的 switch 語法。

<code>#!/bin/bash
fn() {
case "$1" in
# 匹配 a 或 c
"a" | "c")
echo "輸入參數為 $1 "
;;

"b")
echo "輸入參數2為 2 "
;;

# 匹配其它
*)
echo "輸入其他"
;;
esac
}

fn a1/<code>

select

select 還是比較有用的,有的時候我們需要用戶選擇需要執行的命令,

<code>#!/bin/bash
Operations=("start" "stop" "restart")
# 輸入提示
PS3="Please input the number of operation :"
select operation in ${Operations[@]}; do
case ${operation} in
"start")
echo "執行 start 操作。"
break
;;
"stop")
echo "執行 stop 操作。"
break
;;
"restart")
echo "執行 restart 操作。"
break
;;
*)
echo "輸入錯誤,請重新輸入..."
;;
esac
done/<code>

運行上述腳本的之後,然後你輸入 1 ,會執行 start 操作。很方便,不需要用戶輸入參數了。

<code>1) start
2) stop
3) restart
Please input the number of operation :/<code>

啟動和關閉 java 服務腳本

啟動

參考了 nacos 的腳本目錄和編寫。

<code>#!/bin/sh

# 設置 springboot 啟動的 jar 包
JAR_NAME="proximab-server"


# 設置 jvm 配置信息
JAVA_OPT="-server -Xms1g -Xmx1g -Xmn512m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"

# 設置 gc 日誌相關
JAVA_OPT="${JAVA_OPT} -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${BASE_DIR}/logs/java_heapdump.hprof"

#
# 只需要考慮修改以上參數就行
#

# 判斷是否配置 JAVA_HOME
if [ -z "${JAVA_HOME}" ]; then
echo "please set JAVA_HOME";
exit 1;
fi

# 設置執行的 java 路徑
export JAVA="${JAVA_HOME}/bin/java"

# 設置項目的根路徑
export BASE_DIR=`cd $(dirname $0)/..; pwd`

# 設置配置文件位置,並且自定義自己的配置文件位置
DEFAULT_SEARCH_LOCATIONS="classpath:/,classpath:/config/,file:./,file:./config/"
CUSTOM_SEARCH_LOCATIONS=${DEFAULT_SEARCH_LOCATIONS},file:${BASE_DIR}/conf/

# 設置啟動的配置文件
JAVA_OPT="-jar ${JAVA_OPT} ${BASE_DIR}/lib/${JAR_NAME}.jar --spring.config.location=${CUSTOM_SEARCH_LOCATIONS}"
JAVA_OPT="${JAVA_OPT} --logging.config=${BASE_DIR}/conf/logback-spring.xml"
# 配置日誌文件生成的位置
JAVA_OPT="${JAVA_OPT} --logging.log-path=${BASE_DIR}/logs"



# 項目日誌位置
if [ ! -d "${BASE_DIR}/logs" ]; then
mkdir ${BASE_DIR}/logs
fi

# 啟動時輸出啟動的日誌
if [ ! -f "${BASE_DIR}/logs/start.out" ]; then
touch "${BASE_DIR}/logs/start.out"
fi

# 將啟動的 java 相關的配置信息打印到日誌文件中
echo "${JAVA} ${JAVA_OPT} ${BASE_DIR}/lib/${JAR_NAME}" > ${BASE_DIR}/logs/start.out 2>&1 &

# 將錯誤日誌 和 正常輸入日誌重定向到 start.out 中去
nohup ${JAVA} ${JAVA_OPT} >> ${BASE_DIR}/logs/start.out 2>&1 &

echo "${JAR_NAME} is starting,you can check the ${BASE_DIR}/logs/start.out"/<code>

關閉

<code>#!/bin/sh

# 設置 jar 名稱
JAR_NAME="proximab-server"


#
# 只需要考慮修改以上參數就行
#


# 設置項目的根路徑
export BASE_DIR=`cd $(dirname $0)/..; pwd`

PID=`ps -ef | grep -i "${JAR_NAME}.jar" | grep java | grep -v grep | awk '{print $2}'`

if [ -z "$PID" ] ; then
echo "No ${JAR_NAME} running."
exit 1;
fi


echo "The ${JAR_NAME} is running ,PID is (${PID}) ..."

kill ${PID}

if [ $? != 0 ]; then
echo "kill ${JAR_NAME} fail"
exit 1;
fi
echo "kill ${JAR_NAME} is OK, PID (${PID} shutdown )"/<code>

本文由博客一文多發平臺 OpenWrite 發佈!


分享到:


相關文章: