Shell程式設計

Linux小菜鸟發表於2024-12-03
# 自定義vim,以便於更好的編寫shell
[root@kylin-xu ~]# vim .vimrc
set ts=4
set ai
autocmd BufNewFile *.sh exec ":call SetTitle()"

func SetTitle()
    if expand("%:e") == 'sh'
        call setline(1,"#!/bin/bash")
        call setline(2,"#")
        call setline(3,"#****************************************************")
        call setline(4,"#Author:            xu")
        call setline(5,"#QQ:                123456")
        call setline(6,"#Date:              ".strftime("%Y-%m-%d"))
        call setline(7,"#FileName:          ".expand("%"))
        call setline(8,"#URL:               https://www.cnblogs.com/xuruizhao")
        call setline(9,"#Description:       test")
        call setline(10,"#Copyright(C):     ".strftime("%Y")." All right")
        call setline(11,"#***************************************************")
        call setline(12,"")
        call setline(13,"")
        call setline(14,"")
    endif
endfunc
autocmd BufNewFile * normal G

一、 shell指令碼語言的基本用法

【1】、shell指令碼的用途

  • 將簡單的命令組合完成複雜的工作,自動化執行命令,提高工作效率
  • 減少手工命令的輸入,一定程度上避免人為錯誤
  • 將軟體或應用的安裝及配置實現標準化
  • 用於實現日常性的,重複性的,非互動式的運維工作,如:檔案打包壓縮備份,監控系統執行狀態並實現告警等

【2】、shell指令碼格式

shell是基於過程式、解釋執行的語言。

shell指令碼是包含一些命令或宣告,並符合一定格式的文字檔案。

一個shell指令碼檔案中主要包含以下內容

  • 各種系統命令的組合
  • 資料儲存:變數、陣列
  • 表示式:a + b
  • 控制語句:if
格式要求:首行shebang機制
#!/bin/bash 
#!/usr/bin/python
#!/usr/bin/perl 
#!/usr/bin/ruby
#!/usr/bin/lua

【3】、shell指令碼建立過程

  1. 用編輯器建立新檔案,首行必須是 shell 宣告(shebang),編輯完成後儲存退出

  2. 新增可執行許可權

  3. 執行指令碼

#新建檔案
[root@kylin-xu ~]# vim test.sh
#!/bin/bash
echo "hello world" 

# 如果沒有x許可權,在執行時就需要加上直譯器
[root@kylin-xu ~]# bash test.sh 
hello world

#加可執行許可權,透過絕對路徑和相對路徑執行
[root@kylin-xu ~]# ./test.sh
-bash: ./test.sh: 許可權不夠
[root@kylin-xu ~]# chmod +x test.sh 
[root@kylin-xu ~]# ./test.sh
hello 

# 我們也可以將指令碼路徑加到PATH中,然後就可以直接執行指令碼了
[root@kylin-xu ~]# test.sh
-bash: test.sh:未找到命令
[root@kylin-xu ~]# PATH=`pwd`:$PATH
[root@kylin-xu ~]# echo $PATH
/root:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/root/bin
[root@kylin-xu ~]# test.sh
hello world

【4】、第一個shell指令碼

[root@kylin-xu scripts]# vim hello.sh
#!/bin/bash
echo hello world
echo "hello world"
echo 'hello world'

[root@kylin-xu scripts]# bash hello.sh 
hello world
hello world
hello world

當前shell執行,和子shell執行

# 父子shell的關係
root@xu-ubuntu:~/scripts# cat 01.sh 
#!/bin/bash

echo hello world
echo 'hello world'
echo "hello world"
sleep 100

# bash 01.sh是在當前shell執行
root@xu-ubuntu:~/scripts# bash 01.sh 
hello world
hello world
hello world
root@xu-ubuntu:~# pstree -p
 ├─sshd(29225)─┬─sshd(83993)───sshd(84113)───bash(84114)───su(84241)───bash(84256)───bash(84456)───sleep(84457)
# ./01.sh 是在子shell執行
root@xu-ubuntu:~/scripts# ./01.sh 
hello world
hello world
hello world

root@xu-ubuntu:~# pstree -p
├─sshd(29225)─┬─sshd(83993)───sshd(84113)───bash(84114)───su(84241)───bash(84256)───01.sh(84468)───sleep(84469)

本地執行遠端指令碼 - 指令碼檔案在遠端主機,在本機執行並在本機生效,執行結果影響的是本機

test.sh指令碼在192.168.121.99主機上
[root@kylin-xu html]# cat /var/www/html/test.sh 
#!/bin/bash

echo 'haha'
touch /root/1.txt

在192.168.121.88上執行並生效
root@xu-ubuntu:~/scripts# curl http://192.168.121.99/test.sh | bash
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   375  100   375    0     0  72032      0 --:--:-- --:--:-- --:--:-- 93750
haha
root@xu-ubuntu:~/scripts# ll /root/1.txt 
-rw-r--r-- 1 root root 0 Nov 24 19:24 /root/1.txt

指令碼在本地,我在遠端主機執行本地指令碼,指令碼的輸出結果是在本地,但是指令碼的作用物件是遠端主機

指令碼在本地,在本地執行
root@xu-ubuntu:~/scripts# vim 02.sh
#!/bin/bash

hostname
hostname -I
root@xu-ubuntu:~/scripts# bash 02.sh 
xu-ubuntu
192.168.121.88 

# 連線上遠端主機,執行本地shell,shell的結果輸出在本地,但是卻作用於遠端主機
root@xu-ubuntu:~/scripts# ssh 192.168.121.99 /bin/bash < 02.sh 
kylin-xu
192.168.121.99 

【5】、shell指令碼除錯

寫一個指令碼,獲取主機系統資訊,包括CPU型號,記憶體大小,硬碟大小,作業系統版本這四個指標

# version1.0
root@xu-ubuntu:~/scripts# vim sys_ver1.0.sh
#! /bin/bash
lscpu  | grep 'Model name'  | sed -r 's#.*:[ ]+(.*$)#\1#'
free -h | awk 'NR==2{print $2}' 
lsblk | awk '/^sda/{print $(NF-2)}' 
grep 'PRETTY_NAME' /etc/os-release | sed -r 's#.*"(.*)"$#\1#'
root@xu-ubuntu:~/scripts# chmod +x sys_ver1.0.sh 
root@xu-ubuntu:~/scripts# ./sys_ver1.0.sh 
13th Gen Intel(R) Core(TM) i5-13500HX
3.8Gi
50G
Ubuntu 22.04.5 LTS


# version2
root@xu-ubuntu:~/scripts# vim sys_ver2.0.sh
#!/bin/bash

echo -e "CPU info: \c"
lscpu  | grep 'Model name'  | sed -r 's#.*:[ ]+(.*$)#\1#'
echo -e "free total: \c"
free -h | awk 'NR==2{print $2}'
echo -e "hd size: \c"
lsblk | awk '/^sda/{print $(NF-2)}'
echo -e "sys info: \c"
grep 'PRETTY_NAME' /etc/os-release | sed -r 's#.*"(.*)"$#\1#'
root@xu-ubuntu:~/scripts# bash sys_ver2.0.sh 
CPU info: 13th Gen Intel(R) Core(TM) i5-13500HX
free total: 3.8Gi
hd size: 50G
sys info: Ubuntu 22.04.5 LTS


# version3,給輸出加上顏色
root@xu-ubuntu:~/scripts# vim sys_ver2.0.sh
#!/bin/bash

echo -e "CPU info: \c"
echo -e "\E[1;32m`lscpu  | grep 'Model name'  | sed -r 's#.*:[ ]+(.*$)#\1#'`\E[0m"

echo -e "free total: \c"
echo -e "\E[1;32m`free -h | awk 'NR==2{print $2}'`\E[0m"

echo -e "hd size: \c"
echo -e "\E[1;32m`lsblk | awk '/^sda/{print $(NF-2)}'`\E[0m"

echo -e "sys info: \c"
echo -e "\E[1;32m`grep 'PRETTY_NAME' /etc/os-release | sed -r 's#.*"(.*)"$#\1#'`\E[0m"
root@xu-ubuntu:~/scripts# bash sys_ver2.0.sh 
CPU info: 13th Gen Intel(R) Core(TM) i5-13500HX
free total: 3.8Gi
hd size: 50G
sys info: Ubuntu 22.04.5 LTS

執行前先做語法檢查

此選項只能檢測指令碼中的語法錯誤,不能檢測命令錯誤,也不會執行指令碼

bash -n
root@xu-ubuntu:~/scripts# vim 03.sh
#!/bin/bash

echo 123
echo 456
echo "ased
root@xu-ubuntu:~/scripts# bash -n 03.sh 
03.sh: line 16: unexpected EOF while looking for matching `"'
03.sh: line 17: syntax error: unexpected end of file

除錯並執行,查詢命令的錯誤

逐行輸出命令,並輸出執行結果

bash -x
root@xu-ubuntu:~/scripts# vim 04.sh
echo '123'
id
hostname
hostname -I
echo 45
root@xu-ubuntu:~/scripts# bash -x 04.sh 
+ echo 123
123
+ id
uid=0(root) gid=0(root) groups=0(root)
+ hostname
xu-ubuntu
+ hostname -I
192.168.121.88 
+ echo 456
456
# 獲取兩天後的日期
root@xu-ubuntu:~/scripts# date +%F
2024-11-24
root@xu-ubuntu:~/scripts# date +%F --date="+2 day"
2024-11-26

總結:指令碼錯誤常見的有三種

  1. 語法錯誤:會導致後續的命令不繼續執行,可以用 bash -n 檢查錯誤,提示的出錯行數不一定是準確的
  2. 命令錯誤:預設後續的命令還會繼續執行,用 bash -n 無法檢查出來 ,可以使用 bash -x 進行觀察
  3. 邏輯錯誤:只能使用 bash -x 進行觀察

【6】、變數

1、變數型別

變數型別:

  • 內建變數,如:PS1,PATH,UID,HOSTNAME,$$,BASHPID,PPID,$?,HISTSIZE
  • 使用者自定義變數

不同的變數存放的資料不同,決定了以下

  • 資料儲存方式
  • 參與的運算
  • 表示的資料範圍

變數資料型別:

  • 字元
  • 數值:整型、浮點型,bash 不支援浮點數
  • 布林
  • 指標
  • 結構體:自定義的複合型別的資料型別
  • ........

2、 Shell中變數命名法則

命名要求

  • 區分大小寫
  • 不能使用程式中的保留字和內建變數:如:if, for
  • 只能使用數字、字母及下劃線,且不能以數字開頭,注意:不支援短橫線 “ - ”,和主機名相反

命名習慣

  • 見名知義,用英文單詞命名,並體現出實際作用,不要用簡寫,如:ATM
  • 變數名大寫
  • 區域性變數小寫
  • 函式名小寫
  • 大駝峰StudentFirstName,由多個單片語成,且每個單詞的首字母是大寫,其它小寫
  • 小駝峰studentFirstName ,由多個單片語成,第一個單詞的首字母小寫,後續每個單詞的首字母
  • 是大寫,其它小寫
  • 下劃線: student_first_name

3、變數的定義和引用

變數的生效範圍等標準劃分變數型別

  • 普通變數:生效範圍為當前shell程序;對當前shell之外的其它shell程序,包括當前shell的子shell 程序均無效
  • 環境變數:生效範圍為當前shell程序及其子程序
  • 本地變數:生效範圍為當前shell程序中某程式碼片斷,通常指函式

(1)、變數賦值

name='value'

value 可以是以下多種形式

name='root' #直接字串
name="$USER" #變數引用
name=`COMMAND` #命令引用
name=$(COMMAND) #命令引用

root@xu-ubuntu:~/scripts# name="tom"
root@xu-ubuntu:~/scripts# echo $name
tom
root@xu-ubuntu:~/scripts# name=$USER
root@xu-ubuntu:~/scripts# echo $name
root
root@xu-ubuntu:~/scripts# name=`hostname`
root@xu-ubuntu:~/scripts# echo $name
xu-ubuntu
root@xu-ubuntu:~/scripts# name=$(hostname)
root@xu-ubuntu:~/scripts# echo $name
xu-ubuntu

注意:變數賦值是臨時生效,當退出終端後,變數會自動刪除,無法持久儲存,指令碼中的變數會隨著指令碼結束,也會自動刪除

(2)、變數引用

$name
${name}

弱引用和強引用

  • "$name" 弱引用,其中的變數引用會被替換為變數值
  • '$name' 強引用,其中的變數引用不會被替換為變數值,而保持原字串

變數的間接賦值和引用

root@xu-ubuntu:~/scripts# Ti=cto
root@xu-ubuntu:~/scripts# 
root@xu-ubuntu:~/scripts# name=wang
root@xu-ubuntu:~/scripts# Ti=$name
root@xu-ubuntu:~/scripts# echo $Ti
wang
root@xu-ubuntu:~/scripts# name=xu
root@xu-ubuntu:~/scripts# echo $Ti
wang

變數追加值

root@xu-ubuntu:~# TI=cto
root@xu-ubuntu:~# TI+=:xu
root@xu-ubuntu:~# echo $TI
cto:xu

在指令碼中使用變數

COLOR=33

echo -e "CPU info: \c"
echo -e "\E[1;${COLOR}m`lscpu  | grep 'Model name'  | sed -r 's#.*:[ ]+(.*$)#\1#'`\E[0m"

echo -e "free total: \c"
echo -e "\E[1;${COLOR}m`free -h | awk 'NR==2{print $2}'`\E[0m"

echo -e "hd size: \c"
echo -e "\E[1;${COLOR}m`lsblk | awk '/^sda/{print $(NF-2)}'`\E[0m"

echo -e "sys info: \c"
echo -e "\E[1;${COLOR}m`grep 'PRETTY_NAME' /etc/os-release | sed -r 's#.*"(.*)"$#\1#'`\E[0m"


root@xu-ubuntu:~/scripts# bash sys_ver2.0.sh 
CPU info: 13th Gen Intel(R) Core(TM) i5-13500HX
free total: 3.8Gi
hd size: 50G
sys info: Ubuntu 22.04.5 

(3)、刪除變數

root@xu-ubuntu:~/scripts# name=xixi
root@xu-ubuntu:~/scripts# echo $name
xixi
root@xu-ubuntu:~/scripts# unset name
root@xu-ubuntu:~/scripts# echo $name

(4)、顯示系統中定義的所有變數

root@xu-ubuntu:~/scripts# set | grep name=
name=xixi

4、環境變數

環境變數:

  • 可以使子程序(包括孫子程序)繼承父程序的變數,但是無法讓父程序使用子程序的變數
  • 一旦子程序修改從父程序繼承的變數,將會新的值傳遞給孫子程序
  • 一般只在系統配置檔案中使用,在指令碼中較少使用

變數宣告和賦值:

#宣告並賦值
export name=VALUE
declare -x name=VALUE
#或者分兩步實現
name=VALUE
export name

顯示所有環境變數

env
printenv
export
declare -x

檢視指定程序的環境變數

cat /proc/$PID/environ

刪除變數

unset

bash內建的環境變數

PATH
SHELL
USER
UID
HOME
PWD
SHLVL #shell的巢狀層數,即深度
LANG
MAIL
HOSTNAME
HISTSIZE
_   #下劃線,表示前一命令的最後一個引數
root@xu-ubuntu:~# var1=123;export var1
root@xu-ubuntu:~# var2=456
# var1是環境變數,var2是普通變數
root@xu-ubuntu:~# vim scripts/en.sh
echo $var1
echo $var2
echo $PS1
echo $PATH

root@xu-ubuntu:~# ./scripts/en.sh   # 子shell執行
123


/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

在父shell中定義的環境變數,在子shell中同樣也可以引用

5、只讀變數

只讀變數:只能宣告定義,但後續不能修改和刪除,即常量

宣告只讀變數

readonly name
declare -r name

檢視只讀變數

readonly [-p]
declare -r
root@xu-ubuntu:~# readonly name
root@xu-ubuntu:~# name="asd"
-bash: name: readonly variable

root@xu-ubuntu:~# readonly name="aaa"
root@xu-ubuntu:~# echo $name
aaa
root@xu-ubuntu:~# name=haha
-bash: name: readonly variable

6、位置變數

位置變數:在bash shell中內建的變數, 在指令碼程式碼中呼叫透過命令列傳遞給指令碼的引數

$1,$2,...,$9,${10}      #對應第1個、第2個等引數,shift [n]換位置
$0             #shell指令碼的檔名
$*             #傳遞給指令碼的所有引數,全部引數合為一個字串
$@             #傳遞給指令碼的所有引數,每個引數為獨立字串
$#             #傳遞給指令碼的引數的個數
$$             #輸出當前的PID
root@xu-ubuntu:~# vim scripts/arg.sh
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "10st arg is ${10}"
echo "11st arg is ${11}"
echo "The number of arg is $#"
echo "All args are $*"
echo "All args are $@"
echo "The scriptname is `basename $0`"

[root@ubuntu2204 ~]# bash /data/scripts/arg.sh {a..z}
1st arg is a
2st arg is b
3st arg is c
10st arg is j
11st arg is k
The number of arg is 26
All args are a b c d e f g h i j k l m n o p q r s t u v w x y z
All args are a b c d e f g h i j k l m n o p q r s t u v w x y z
The scriptname is arg.sh

$*和$@的區別

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

for j in "$@";do
        echo $j
done
root@xu-ubuntu:~# bash scripts/05.sh 1 2 3
1 2 3
1
2
3

$*:將接收到的引數看為一個整體

$@:將接收到每一個引數看作一個單獨的引數

範例: 利用軟連結實現同一個指令碼不同功能

root@xu-ubuntu:~# ll /usr/sbin/shutdown
lrwxrwxrwx 1 root root 14 Nov 22  2023 /usr/sbin/shutdown -> /bin/systemctl*
root@xu-ubuntu:~# ll /usr/sbin/reboot
lrwxrwxrwx 1 root root 14 Nov 22  2023 /usr/sbin/reboot -> /bin/systemctl*
root@xu-ubuntu:~/scripts# cat test.sh 
echo $0

root@xu-ubuntu:~/scripts# ln -s test.sh a.sh
root@xu-ubuntu:~/scripts# bash a.sh 
a.sh

root@xu-ubuntu:~/scripts# ln -s test.sh b.sh
root@xu-ubuntu:~/scripts# bash b.sh 
b.sh
if [ $0 == "a.sh" ];then
        echo "aaa"
elif [ $0 == "b.sh" ];then
        echo "bbb"
fi
root@xu-ubuntu:~/scripts# bash a.sh 
aaa
root@xu-ubuntu:~/scripts# bash b.sh 
bbb

7、退出狀態碼變數

程序執行後,將使用變數 $? 儲存狀態碼的相關數字,不同的值反應成功或失敗,$?取值範例 0-255

$? 		   #0代表成功,1-255代表失敗
root@xu-ubuntu:~/scripts# echo $?
0
root@xu-ubuntu:~/scripts# ping -c1 -w1 baidu.afasfsascom >/dev/null 2>&1
root@xu-ubuntu:~/scripts# echo $?
2

使用者可以在指令碼中使用以下命令自定義退出狀態碼

exit [n]

注意:

  • 指令碼中一旦遇到exit命令,指令碼會立即終止;終止退出狀態取決於exit命令後面的數字
  • 如果exit後面無數字,終止退出狀態取決於exit命令前面命令執行結果
  • 如果沒有exit命令,即未給指令碼指定退出狀態碼,整個指令碼的退出狀態碼取決於指令碼中執行的最後一 條命令的狀態碼

8、指令碼安全$-

root@xu-ubuntu:~/scripts# echo $-
himBHs
選項 含義
h 開啟hash表快取外部命令路徑
i 當前是互動shell
m 開啟監控模式,可透過 Job control來控制程序的停止、繼續,後臺
B 是否支援大括號擴充套件
H 是否可用 ! 快速展開history命令
root@xu-ubuntu:~/scripts# echo $-
himBHs
root@xu-ubuntu:~/scripts# set +B
root@xu-ubuntu:~/scripts# echo $-
himHs
root@xu-ubuntu:~/scripts# echo {1..4}
{1..4}
root@xu-ubuntu:~/scripts# set -B
root@xu-ubuntu:~/scripts# echo $-
himBHs
root@xu-ubuntu:~/scripts# set +h
root@xu-ubuntu:~/scripts# hash
-bash: hash: hashing disabled

終端中的$- 和 shell指令碼中的$- 不同

root@xu-ubuntu:~/scripts# cat 07.sh 
echo $-
root@xu-ubuntu:~/scripts# bash 07.sh 
hB
root@xu-ubuntu:~/scripts# echo $-
himBHs

【7】、格式化輸出printf

相當於增強版的 echo, 實現豐富的格式化輸出

格式

printf format args..

image-20241125094827824

常用格式替換符

替換符 功能
%s 字串
%d,%i 十進位制整數
%f 浮點型別
%c ASCII字元,即顯示對應引數的第一個字元
%b 相對應的引數中包含跳脫字元時,可以使用此替換符進行替換,對應的跳脫字元會被轉義
%o 八進位制
%u 無符號數
%x 十六進位制數值(a-f)
%X 十六進位制數值(A-F)
%% 表示%本身

常用跳脫字元

轉義符 功能
\a 警告字元通常為ASCII的BEL字元
\b 退格
\f 換頁
\n 換行
\r 回車
\t 水平製表符
\v 垂直製表符
\ 表示 \ 本身
root@xu-ubuntu:~/scripts# printf "%s\n"  1 2 3
1
2
3
root@xu-ubuntu:~/scripts# printf "%f\n" 1 2
1.000000
2.000000
# .3f表示儲存3位小數
root@xu-ubuntu:~/scripts# printf "%.3f\n" 1.1234 2.12345
1.123
2.123

# 最後的echo表示換行的意思
root@xu-ubuntu:~/scripts# printf "(%s)---" 1 2 3 ;echo
(1)---(2)---(3)---
root@xu-ubuntu:~/scripts# printf "%-10s %-10s %-4s %s \n" 姓名 性別 年齡 體重 小明 男 20 70 小紅 女 18 50
# - 表示左對齊,不加- 是右對齊
姓名     性別     年齡 體重 
小明     男        20   70 
小紅     女        18   50 
# 進位制轉換
14root@xu-ubuntu:~/scripts# printf "%o" 12;echo 
14

0root@xu-ubuntu:~/scripts# printf "%x" 999 ;echo
3e7
root@xu-ubuntu:~/scripts# printf "%X" 999 ;echo
3E7

【8】、運算子

1、算術運算子

Shell允許在某些情況下對算術表示式進行求值,比如:let和declare 內建命令,(( ))複合命令和算術擴充套件。求值以固定寬度的整數進行,不檢查溢位,儘管除以0 被標記為錯誤。運算子及其優先順序,關聯性和值與C語言相同。以下運算子列表分組為等優先順序運算子級別。級別按降序排列優先。

注意:bash 只支援整數,不支援小數

+ -                               #addition, subtraction
* / %                             #multiplication, division, remainder, %表示取模,即取餘數,示例:9%4=1,5%3=2 
i++ i--                           #variable post-increment and post-decrement
++i --i                           #variable pre-increment and pre-decrement
= *= /= %= += -= <<= >>= &= ^= |=   #assignment
- +                                 #unary minus and plus
! ~                                 #logical and bitwise negation
**                                  #exponentiation 乘方,即指數運算
<< >>                               #left and right bitwise shifts
<= >= < >                           #comparison
== !=                               #equality and inequality
&                                   #bitwise AND
|                                   #bitwise OR
^                                   #bitwise exclusive OR
&&                                  #logical AND
||                                  #logical OR
expr?expr:expr                      #conditional operator
expr1 , expr2                       #comma

乘法符號有些場景中需要轉義

實現算術運算

let var=expression
((var=expression))
var=$[expression]
var=$((expression))
var=$(expr arg1 arg2 ...)
declare -i var=number
echo expression|bc
# 如果不進行轉義是無法進行運算的
root@xu-ubuntu:~/scripts# i=3
root@xu-ubuntu:~/scripts# j=4
root@xu-ubuntu:~/scripts# echo i+j
i+j

# 進行轉義
root@xu-ubuntu:~/scripts# echo $[i+j]
7
root@xu-ubuntu:~/scripts# echo $((i+j))
7
root@xu-ubuntu:~/scripts# let k=i+j
root@xu-ubuntu:~/scripts# echo $k
7
# expr進行算數。在進行乘法運算時要進行轉義,而且必須要有空格
root@xu-ubuntu:~/scripts# expr 3 + 4
7
root@xu-ubuntu:~/scripts# expr 3 \* 4
12

範例:生成 0 - 49 之間隨機數

$RANDOM 隨機數的範圍 0-32767
root@xu-ubuntu:~/scripts# echo $[RANDOM%50]
33
root@xu-ubuntu:~/scripts# echo $[RANDOM%50]
5
root@xu-ubuntu:~/scripts# echo $[RANDOM%50]
44

# 隨機字型顏色
root@xu-ubuntu:~/scripts# echo -e "\033[1;$[RANDOM%7+31]mhello\033[0m"
hello

增強型賦值:

-=                # i-=j 相當於 i=i-j
*=
/=
%=
++               # i++,++i   相當於 i=i+1
--               # i--,--i   相當於 i=i-1


root@xu-ubuntu:~/scripts# a=10
root@xu-ubuntu:~/scripts# let a+=20
root@xu-ubuntu:~/scripts# echo $a
30
root@xu-ubuntu:~/scripts# echo $[a+=10]
40
root@xu-ubuntu:~/scripts# echo $((a+=10))
50

i++與++i

# i++先賦值再+1
root@xu-ubuntu:~/scripts# i=1
root@xu-ubuntu:~/scripts# echo j=i++
j=i++
root@xu-ubuntu:~/scripts# let j=i++
root@xu-ubuntu:~/scripts# echo $i
2
root@xu-ubuntu:~/scripts# echo $j
1

# ++i先+1再賦值
root@xu-ubuntu:~/scripts# i=1
root@xu-ubuntu:~/scripts# let  j=++i
root@xu-ubuntu:~/scripts# echo $j
2
root@xu-ubuntu:~/scripts# echo $i
2

2、邏輯運算

image-20241125111431965

true、false

bool值 二進位制表示 含義
true 1
false 0

與:&和相與結果為0,和1相與結果保留原值,一假則假,全真才真

或:|和1相或結果為1,和0相或結果保留原值,一真則真,全假才假

異或:

異或的兩個值,相同為假,不同為真。兩個數字X,Y異或得到結果Z,Z再和任意兩者之一X異或,將得

出另一個值Y

root@xu-ubuntu:~/scripts# false
root@xu-ubuntu:~/scripts# echo $?
1
root@xu-ubuntu:~/scripts# true
root@xu-ubuntu:~/scripts# echo $?
0

# 變數互換
root@xu-ubuntu:~/scripts# x=10
root@xu-ubuntu:~/scripts# y=20
root@xu-ubuntu:~/scripts# let x=x^y
root@xu-ubuntu:~/scripts# let y=x^y
root@xu-ubuntu:~/scripts# let x=x^y
root@xu-ubuntu:~/scripts# echo $x $y
20 10

短路運算

短路&&

cmd1&&cmd2

當 cmd1 的結果為 false 時,整個表示式就是 false, cmd2 不參與運算,這就是所謂的短路

當 cmd1 的結果為 true 時,cmd2 才參與運算, 看cmd2的結果是真假,在整體判斷 表示式的真假

短路||

cmd1||cmd2

當 cmd1 的結果為 true 時,整個表示式就是 true, cmd2 不參與運算,這就是所謂的短路

當 cmd1 的結果為 false 時,cmd2 參與運算,根據cmd2的結果再去判斷整個表示式的真假

[root@kylin-xu ~]# id tom > /dev/null  2>&1 && echo have || echo no
no

【9】、條件測試命令

條件測試:

判斷某需求是否滿足,需要由測試機制來實現,專用的測試表示式需要由測試命令輔助完成測試過程,

實現評估布林宣告,以便用在條件性環境下進行執行

若真,則狀態碼變數 $? 返回0

若假,則狀態碼變數 $? 返回1

條件測試命令

test expr
[ arg... ]            #同 test
[[ expression ]]      #增加版的[],支援[],支援擴充套件正規表示式和萬用字元

# 注意:[] 中的表示式,前後要有空格

[[ expression ]] 用法
== #左側字串是否和右側的PATTERN相同
   #注意:此表示式用於[[ ]]中,PATTERN為萬用字元
   
=~ #左側字串是否能夠被右側的正規表示式的PATTERN所匹配
   #注意: 此表示式用於[[ ]]中為擴充套件的正規表示式

常用選項:檔案判斷相關

注意:最終結果由使用者對檔案的實際許可權決定,而非檔案屬性決定
-a FILE     #如果檔案存在則為真
-b FILE     #如果檔案為塊特殊檔案則為真
-c FILE     #如果檔案為字元特殊檔案則為真
-d FILE     #如果檔案為目錄則為真
-e FILE     #如果檔案存在則為真
-f FILE     #如果檔案存在且為常規檔案則為真
-g FILE     #如果檔案的組屬性設定開啟則為真
-h FILE     #如果檔案為符號連結則為真
-L FILE     #如果檔案為符號連結則為真
-k FILE     #如果檔案的粘滯 (sticky) 位設定則為真
-p FILE     #如果檔案為命名管道則為真
-r FILE     #如果當前使用者對檔案有可讀許可權為真
-s FILE     #如果檔案存在且不為空則為真
-S FILE     #如果檔案是套接字則為真
-t FD       #如果檔案描述符在一個終端上開啟則為真
-u FILE     #如果檔案設定了SUID特殊許可權則為真
-w FILE     #如果當前使用者對檔案有可寫許可權為真
-x FILE     #如果當前使用者對檔案有可執行許可權為真
-O FILE     #如果當前使用者是檔案屬主為真
-G FILE     #如果當前使用者對檔案屬組為真
-N FILE     #如果檔案上次被讀取之後修改過則為真
FILE1 -nt FILE2 #如果 file1 檔案新於 file2 檔案 則為真(根據修改日期)
FILE1 -ot FILE2 #如果 file1 檔案舊於 file2 檔案則為真
FILE1 -ef FILE2 #如果 file1 檔案是 file2 檔案的硬連結則為真

[root@kylin-xu ~]# [ -a /root/1.txt  ] && echo exist || echo not
not
[root@kylin-xu ~]# touch 1.txt
[root@kylin-xu ~]# [ -a /root/1.txt  ] && echo exist || echo not
exist

常用選項:字串判斷相關

在比較字串時,建議放在""中

-z STRING     #判斷字串是否為空,為空則為真
-n STRING     #如果字串不為空則為真
STRING #同上  

[root@kylin-xu ~]# [[ -z "" ]]
[root@kylin-xu ~]# echo $?
0
[root@kylin-xu ~]# [[ -z "asdadasd" ]]
[root@kylin-xu ~]# echo $?
1
[root@kylin-xu ~]# [[ -n "" ]]
[root@kylin-xu ~]# echo $?
1
[root@kylin-xu ~]# [[ -n "asds" ]]
[root@kylin-xu ~]# echo $?
0


#兩個字串變數比較,一定要有空格,如果沒有空格,就變成賦值了
STRING1 = STRING2    #如果 string1 和 string2 字串相同則為真
STRING1 != STRING2   #如果 string1 和 string2 字串不相同則為真
STRING1 < STRING2    #只比較第一個字元,以字母表順序來比較,string1在string2 之前則為真,< 要轉義
STRING1 > STRING2    #只比較第一個字元,以字母表順序來比較,string1在string2 之後則為真, > 要轉義

[root@kylin-xu ~]# [ "s" == "s" ] &&  echo true
true
[root@kylin-xu ~]# [ "s" == "s" ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ "s" == "ssss" ] &&  echo true || echo false
false
[root@kylin-xu ~]# [ "s" \< "i" ] &&  echo true || echo false
false
[root@kylin-xu ~]# [ "s" \> "i" ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ "s" != "i" ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ "s" != "s" ] &&  echo true || echo false
false


# 使用正則進行匹配
[root@kylin-xu ~]# [[ $str =~ [a-z]+ ]];echo $?
0
[root@kylin-xu ~]# [[ $str =~ [0-9]+ ]];echo $?
1

常用選項:數學相關

#數字相關 格式 arg1 OP arg2
-eq                    #等於
-ne                    #不等於
-lt                    #小於
-le                    #小於等於
-gt                    #大於
-ge                    #大於等於
#算術表示式比較
==                    #相等
!=                    #不相等
<=                    #小於或等於
>=                    #大於或等於
<                     #小於
>                     #大於


[root@kylin-xu ~]# [ 10 -gt 9 ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ 10 -lt 9 ] &&  echo true || echo false
false
[root@kylin-xu ~]# [ 10 -lt 99 ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ 10 -eq 10 ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ 10 -ne 100 ] &&  echo true || echo false
true
[root@kylin-xu ~]# [ 10 -ne 10 ] &&  echo true || echo false
false

常用選項:其它

-o OPTION                  #如果在shell 中開啟了某項,則為真
-v VAR                     #如果變數被設定為為真
-R VAR                     #如果變數被設定且被引用則為真
! EXPR                     #表示式結果取反
EXPR1 -a EXPR2             #如果 expr1 和 expr2 都為真則為真 &&
EXPR1 -o EXPR2             #如果 expr1 和 expr2 有一個為真則為真

[root@kylin-xu ~]# x=100
[root@kylin-xu ~]# test -v x
[root@kylin-xu ~]# [ -v x ] && echo exist || echo "not exists"
exist

【10】、關於{} 和 ()

( cmd1;cmd2;... ) 和 { cmd1;cmd2;...; } 都可以將多個命令組合在一起,批次執行
( list ) 會開啟子shell,並且list中變數賦值及內部命令執行後,將不再影響後續的環境
{ list; } 不會開啟子shell, 在當前shell中執行,會影響當前shell環境,左側要有空格,右側要有; 結束
[root@kylin-xu ~]# echo $BASHPID
553624
# 小括號開啟子程序
[root@kylin-xu ~]# (echo $BASHPID ; str="aaa";echo $str);echo $str
592898
aaa
abc

# 大括號不會開子程序
[root@kylin-xu ~]# { echo $BASHPID ; str="aaa";echo $str; };echo $str
553624
aaa
aaa

練習

# 1. 編寫指令碼 argsnum.sh,接受一個檔案路徑作為引數;如果引數個數小於1,則提示使用者“至少應該給 一個引數”,並立即退出;如果引數個數不小於1,則顯示第一個引數所指向的檔案中的空白行數
#! /bin/bash
if [ "$#" -lt "1" ];then
        echo "至少應該輸入一個變數"
        exit
fi

sed '/^$/p' -n "$1"|wc -l

#編寫指令碼 hostping.sh,接受一個主機的IPv4地址做為引數,測試是否可連通。如果能ping通,則提 示使用者“該IP地址可訪問”;如果不可ping通,則提示使用者“該IP地址不可訪問”
if [[ $1 =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])$ ]];then 
        ping $1 -c1 -w1 > /dev/null  2>&1 && echo "該ip地址可以訪問" || echo "該IP地址不能訪問"
fi

#編寫指令碼 checkdisk.sh,檢查磁碟分割槽空間和inode使用率,如果超過80%,就發廣播警告空間將滿
for i in `df -ih | sed '2,$p' -n | awk '{print $(NF-1),$1}' | column -t | sort -rn | awk -F "[% ]+" '$1>=80{print $2}'`; 
do 
        echo $i"inode將滿"; 
done

for i in `df -h | sed '2,$p' -n | awk '{print $(NF-1),$1}' | column -t | sort -rn | awk -F "[% ]+" '$1>=80{print $2}'`;
do
    echo $i"空間將滿";
done

# 編寫指令碼 per.sh,判斷當前使用者對指定引數檔案,是否不可讀並且不可寫
if [ ! -r $1 -a ! -x $1 ];then
    echo "不可讀,不可寫"
else
    echo "可讀或可寫"
fi

# 編寫指令碼 excute.sh ,判斷引數檔案是否為sh字尾的普通檔案,如果是,新增所有人可執行許可權,否則提示使用者非指令碼檔案
if [ -f $1 ];then
        chmod +x $1
fi

# 編寫指令碼 nologin.sh和 login.sh,實現禁止和允許普通使用者登入系統
nologin.sh
for i in `awk -F: '$3>=500&&$NF=="/bin/bash"{print $1}' /root/passwd`;
do
    usermod -s /sbin/nologin $i
done

login.sh
for i in `awk -F: '$3>=500&&$NF=="/sbin/nologin"{print $1}' /etc/passwd`; 
do 
    usermod -s /bin/bash $i; 
done

【11】、使用read命令來接受輸入

使用read來把輸入值分配給一個或多個shell變數,read從標準輸入中讀取值,給每個單詞分配一個變數,所有剩餘單詞都被分配給最後一個變數,如果變數名沒有指定,預設標準輸入的值賦值給系統內建變數REPLY

[root@kylin-xu ~]# read
aaa
[root@kylin-xu ~]# echo $REPLY
aaa
[root@kylin-xu ~]# read -p "input:" NAME
input:xxx
[root@kylin-xu ~]# echo $NAME
xxx

管道符左右兩側都是子程序

[root@kylin-xu ~]# echo $BASHPID ; { echo $BASHPID; } | { xargs ;echo $BASHPID; } 
654136
663419
663420 

二、bash shell的配置檔案

bash shell的配置檔案很多,可以分成下面類別

【1】、按照生效範圍登入

全域性配置:針對所有使用者皆有效

/etc/profile
/etc/profile.d/*.sh
/etc/bashrc ------------------------ /etc/bash.bashrc #ubuntu  

個人配置:只針對特定使用者有效

~/.bash_profile
~/.bashrc

【2】、shell登入方式分類

1、互動式登入

  • 直接透過終端輸入賬號密碼登入
  • 使用 su - UserName 切換的使用者

配置檔案生效和執行順序:

#放在每個檔案最前
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc 
~/ .bash_ profile
~/ .bashrc
/etc/bashrc                 #此檔案執行兩次
#放在每個檔案最後
/etc/profile.d/*.sh
/etc/bashrc
/etc/profile
/etc/bashrc                #此檔案執行兩次
~/.bashrc
~/.bash_profile

注意:檔案之間的呼叫關係,寫在同一個檔案的不同位置,將影響檔案的執行順序

2、非互動式登入

  • su UserName
  • 圖形介面下開啟的終端
  • 執行指令碼
  • 任何其它的bash例項

執行順序:

/etc/profile.d/*.sh
/etc/bashrc
~/.bashrc

【3】、編輯配置檔案生效

修改profile和bashrc檔案後需生效兩種方法:

  • 重新啟動shell程序
  • source|. 配置檔案

注意:source 會在當前shell中執行指令碼,所有一般只用於執行置檔案,或在指令碼中呼叫另一個指令碼的場景

【4】、Bash退出任務

儲存在~/.bash_logout檔案中(使用者),在退出登入shell時執行

功能:

  • 建立自動備份
  • 清除臨時檔案

三、流程控制

【1】、條件判斷

image-20241126162439949

image-20241126162455880

image-20241126162511888

1、if語法

if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
# if單分支
if [ "$1" = "abc" ];then
    echo "abc"
fi
# if多分枝
str=abc
if [ "$1" = "abc" ];then
    echo "abc"
elif [ "$1" = "def" ];then
    echo "def"
else
    echo "haha"
fi
# if 巢狀
str=abc
if [ "$1" = "abc" ];then
        echo "abc"
        if [ "$2" = "def" ];then
                echo "2 is def"
        else
                echo "2 not def"
        fi
elif [ "$1" = "def" ];then
        echo "def"
else 
        echo "haha"
fi
# 判斷作業系統型別
. /etc/os-release 

if [ $ID = "ubuntu" ];then
        echo "is ubuntu"
elif [[ $ID =~ rhel|centos|rocky ]];then
        echo "rhel"
else
        echo "other system"
fi

2、case語句

case $1 in
"abc")
        echo "is abc"
        ;;
"def")
        echo "is def"
        ;;
"xyz")
        echo "is xyz"
        ;;
"123")
        echo "is 123"
        ;;
*)
        echo "any"
        ;;
esac
read -p "" INPUT
INPUT=`echo  $INPUT | tr "A-Z" "a-z"`
case $INPUT in
y|yes)
        echo "is yes"
        ;;
n|no)
        echo "is no"
        ;;
*)
        echo "none"
        ;;
esac
# 檔案字尾處理
read -p "請輸入檔名:" FILE
FILE=`echo $FILE |sed 's#.*\.(.*$)#\1#g' -r`
case $FILE in
txt)
        echo "is txt file"
        ;;
sh)
        echo "is shell file"
        ;;
conf)
        echo "is configure file"
        ;;
*)
        echo "any"
        ;;
esac

【2】、迴圈控制

將某程式碼段重複執行多次,通常有進入迴圈的條件和退出迴圈的條件

重複執行次數

  • 迴圈次數事先已知
  • 迴圈次數事先未知

常見的迴圈的命令:for, while, until

image-20241129105125888

1、for迴圈

執行機制:

  • 依次將列表中的元素賦值給“變數名”; 每次賦值後即執行一次迴圈體; 直到列表中的元素耗盡,迴圈結束
  • 如果省略 [in WORDS ... ] ,此時使用位置引數變數 in "$@"

for 迴圈列表生成方式:

  • 直接給出列表
  • 整數列表:如
  • 返回列表的命令:如 $(COMMAND)
  • 使用glob,如:*.sh
  • 變數引用,如:$@,$*,$#
# 計算1+2+3+...+100 的結果
sum=0
for i in {1..100}
do
        let sum=$i+$sum
done
echo $sum
# 批次建立使用者
[ $# -eq 0 ] && { echo "你沒有輸入變數";exit 1; }

for user in $@;
do
    id $user &> /dev/null  && echo "$user已經存在" || { useradd $user;echo "$user is create";id $user; }
done
# 99乘法表
flag=$[RANDOM%7+31]
for i in {1..9}
do
    for j in {1..9}
    do
        if [ $j -le $i ];then
            let k=$i*$j
            echo -ne "\E[1;${flag}m$j×$i=$k\E[0m\t"
        else
            echo
            break
        fi
    done
done
echo

有若干只兔和雞,兔和雞加起來一共有100條腿,請寫一個簡單的shell算出兔和雞各多少隻可能組合(假設所有的雞和兔子的腿都是健全的,且兔和雞至少為1只)

# 暴力破解
count=0
sum=0
num=0
for i in {1..100}
do
    for j in {1..100}
    do
        let num++
        let sum=4*$i+2*$j
        if [ $sum == 100 ];then
            echo "$i只兔子,$j只雞"
            let count++
        fi
    done
done
echo "共有$count種組合"
echo "迴圈了$num次"
# 第一次最佳化  加break
count=0
sum=0
num=0
for i in {1..100}
do
    for j in {1..100}
    do
        let num++
        let sum=4*$i+2*$j
        if [ $sum == 100 ];then
            echo "$i只兔子,$j只雞"
            let count++
            break
        fi
    done
done
echo "共有$count種組合"
echo "迴圈了$num次"
# 第二次最佳化
count=0
sum=0
num=0
for i in {1..25}
do
    for j in {1..50}
    do
        let num++
        let sum=4*$i+2*$j
        if [ $sum == 100 ];then
            echo "$i只兔子,$j只雞"
            let count++
            break
        fi
    done
done
echo "共有$count種組合"
echo "迴圈了$num次"
# 生成三角形
read -p "請輸入三角形行數:" line
let num=$line*2-1
for i  in `seq $line`
do
        let star=$i*2-1
        let space=($num-$star)/2
        if [ $space == 0 ];then
                for j in `seq $num`
                do
                        echo -n "*"
                done
        else
                for k in `seq $space`
                do
                        echo -n " "
                done
                let x=$num-$space*2
                for m in `seq $x`
                do
                        echo -n "*"
                done
        fi
        echo 
done
請輸入三角形行數:10
         *
        ***
       *****
      *******
     *********
    ***********
   *************
  ***************
 *****************
*******************
# 進度條
root@xu-ubuntu:~/scripts# for ((i = 0; i <= 100; ++i)); do printf "\e[4D%3d%%" $i;sleep 0.1s; done;echo
100%

2、while迴圈

while CONDITION; do COMMANDS; done

while CONDITION; do
迴圈體
done

while CONDITION
do
 迴圈體
done

說明:

CONDITION:迴圈控制條件;進入迴圈之前,先做一次判斷;每一次迴圈之後會再次做判斷;條件為

“true”,則執行一次迴圈;直到條件測試狀態為“false”終止迴圈,因此:CONDTION一般應該有迴圈控

制變數;而此變數的值會在迴圈體不斷地被修正

進入條件:CONDITION為 true

退出條件:CONDITION為 false

# 死迴圈
while true; do
  迴圈體
done

while : ; do
  迴圈體
done

# 計算1-100
root@xu-ubuntu:~/scripts# i=1
root@xu-ubuntu:~/scripts# while [ $i -le 100 ]; do let sum=$sum+$i;let i++; done
root@xu-ubuntu:~/scripts# echo $sum
5050

國際象棋棋盤

color=41
for i in {1..8}
do
        for j in {1..8}
        do
                echo -ne "\E[0${color}m  \E[0m"
                if [ $color -eq 41 ];then
                        color=43
                else
                        color=41
                fi
        done
        if [ $color -eq 41 ];then
            color=43
        else
            color=41
        fi
        echo
done

while特殊用法,while read

while 迴圈的特殊用法,遍歷檔案或文字的每一行

while read line; do
迴圈體
done < /PATH/FROM/SOMEFILE
while read line
do
        echo $line"---------"
done < /etc/passwd

3、continue和break

continue:結束本次迴圈,進入下一次迴圈

break:結束當前迴圈,跳出當前的迴圈體

四、函式

【1】、函式定義

函式 function

是由若干條shell命令組成的語句塊,實現程式碼重用和模組化程式設計

它與shell程式形式上是相似的,不同的是它不是一個單獨的程序,不能獨立執行,而是shell程式的一部分

函式和shell程式區別

  • Shell程式在子Shell中執行
  • 函式在當前Shell中執行。因此在當前Shell中,函式可對shell中變數進行修改
#語法一
func_name(){
 ...函式體...
}

#語法二
function func_name {
 ...函式體...
}

#語法三
function func_name(){
 ...函式體...
}
func1(){
    echo "func1"
}

function func2(){
    echo  "func2"
}

function func3 {
    echo "func3"
}

func1
func2
func3

【2】、函式呼叫

函式的呼叫方式

  • 可在互動式環境下定義函式
  • 可將函式放在指令碼檔案中作為它的一部分
  • 可放在只包含函式的單獨檔案中

呼叫:函式只有被呼叫才會執行,透過給定函式名呼叫函式,函式名出現的地方,會被自動替換為函式程式碼

函式的生命週期:被呼叫時建立,返回時終止

# 互動式環境下
root@xu-ubuntu:~/scripts# test(){
> echo "hanshu"
> }
root@xu-ubuntu:~/scripts# test
hanshu

root@xu-ubuntu:~/scripts# function test1(){
> echo hanshu1
> }
root@xu-ubuntu:~/scripts# test1
hanshu1

root@xu-ubuntu:~/scripts# function test2 {
> echo hanshu3
> }
root@xu-ubuntu:~/scripts# test2
hanshu3


# 指令碼檔案中
function test1(){
        echo hanshu1
}
function test2 {
        echo hanshu2
}
test3(){
        echo hanshu3
}

test1
test2
test3
root@xu-ubuntu:~/scripts# bash fun.sh 
hanshu1
hanshu2
hanshu3

# 單獨檔案
fun.sh檔案
function test1(){
        echo hanshu1
}
function test2 {
        echo hanshu2
}
test3(){
        echo hanshu3
}

fun1.sh檔案
. fun.sh
test1
test2
test3

root@xu-ubuntu:~/scripts# bash fun1.sh 
hanshu1
hanshu2
hanshu3

【3】、函式巢狀

# 在使用函式巢狀時一定要保證能夠有一個出口,能有一個結束的地方,否則程式會不斷地堆疊,報錯

function test1(){
    echo hanshu1
}
function test2 {
    echo hanshu2
}
test3(){
    echo hanshu3
    test2
}
test3

root@xu-ubuntu:~/scripts# bash fun.sh
hanshu3
hanshu2

【4】、函式返回值

return

函式預設返回值是函式體中最後一條命令的退出狀態碼;

也可以使用return 自定義函式的返回值,在函式中使用 return,return 之後的語句將不會被執行

return 語句 返回值
return 由return前面一行的命令執行結果決定
return 0 無錯誤返回
return 1~255 有錯誤返回
function re1(){
        echo aaa
        return
        echo bbb
}

function re2 {
        echooo ccc
        return
}

function re3 {
        echo ddd
        return 123
}

re1
echo "re1函式的返回值:$?"
echo "==============================="
re2
echo "re2函式的返回值:$?"
echo "==============================="
re3
echo "re3函式的返回值:$?"
echo "==============================="
root@xu-ubuntu:~/scripts# bash return.sh 
aaa
re1函式的返回值:0
===============================
return.sh: line 21: echooo: command not found
re2函式的返回值:127
===============================
ddd
re3函式的返回值:123
===============================

return和exit的區別

return 只會中斷函式執行,但exit 會中斷整個指令碼的執行

【5】、函式引數

函式可以接受引數:

  • 傳遞引數給函式:在函式名後面以空白分隔給定引數列表即可,如:testfunc arg1 arg2 ...
  • 在函式體中當中,可使用$1, $2, ...呼叫這些引數;還可以使用$@, $*, $#等特殊變數
function canshu(){
    echo -e "\E[1;$2m$1\E[0m"
    return
}

yanse=$[RANDOM%7+31]
canshu "nihao" $yanse
echo "canshu的返回值是:$?"
function canshu(){
        for i in $*
        do
                echo $i
        done
}

canshu $*

root@xu-ubuntu:~/scripts# bash hanshucanshu.sh 1 2 3 4 5 6
1
2
3
4
5
6

【6】、函式變數

變數作用域

變數型別 特點
普通變數 只在當前shell程序中有效,函式外定義,可以在函式內修改
環境變數 當前shell和子shell有效
本地變數 作用域在函式內,函式結束會被自動銷燬,只能在函式內定義

在函式中定義本地變數

local NAME=VALUE
# 普通變數可以在函式中被修改
root@xu-ubuntu:~/scripts# cat func1.sh 
#!/bin/bash

function test1(){
        echo "============test1 start============"
        echo $var
        var=789
        echo $var
        echo "============test1 end============"
}
root@xu-ubuntu:~/scripts# cat var.sh 
#!/bin/bash

. func1.sh

var=123

function test2(){
        echo "==========test2 start============"
        echo $var
        var=456
        echo $var
        echo "==========test2 end============"
}
function test3(){
        echo "==========test3 start============"
        echo $var
        var=147
        echo $var
        echo "==========test3 end============"
}

echo $var
test1
echo $var
test2
echo $var
test3
echo $var

root@xu-ubuntu:~/scripts# bash var.sh 
123
============test1 start============
123
789
============test1 end============
789
==========test2 start============
789
456
==========test2 end============
456
==========test3 start============
456
147
==========test3 end============
147
# 本地變數只作用在函式內
var=123

function test2(){
        echo "==========test2 start============"
        echo $var
        local var=456
        echo $var
        echo "==========test2 end============"
}
function test3(){
        echo "==========test3 start============"
        echo $var
        local var=147
        echo $var
        echo "==========test3 end============"
}

echo $var
test2
echo $var
test3
echo $var
# 函式中定義的var 只能在函式中被呼叫,作用域只在函式內
root@xu-ubuntu:~/scripts# bash var.sh 
123
==========test2 start============
123
456
==========test2 end============
123
==========test3 start============
123
147
==========test3 end============
123

【7】、函式遞迴

函式遞迴:

函式直接或間接呼叫自身,注意遞迴層數,可能會陷入死迴圈

遞迴特點:

  • 函式內部自已呼叫自已
  • 必須有結束函式的出口語句,防止死迴圈
# 遞迴實現階乘
function jiecheng(){
    if [ $1 -eq 1 ];then
        echo 1
    else
        echo $[$1 * $(jiecheng $[$1-1])]
    fi 
}
jiecheng $1
# 斐波那契數列第N項的值
fib(){
 if [ $1 -gt 1 ];then
     echo $[ $(fib $[$1-1]) + $(fib $[$1-2]) ]
 else
     echo $1
 fi
}
fib $1

fork炸彈

fork 炸彈是一種惡意程式,它的內部是一個不斷在 fork 程序的無限迴圈,實質是一個簡單的遞迴程式。

由於程式是遞迴的,如果沒有任何限制,這會導致這個簡單的程式迅速耗盡系統裡面的所有資源

:(){ :|:& };:
bomb(){ bomb | bomb & };bomb

五、其他指令碼相關工具

【1】、trap訊號捕捉

trap 命令可以捕捉訊號,修改訊號原來的功能,實現自定義功能

在指令碼或程式的執行過程中,我們可以透過傳送訊號的方式,打斷或終止程式的執行過程,為了避免這種情況,我們可以使用訊號捕捉,來自定義訊號處理。

#常用選項
-l #顯示所有訊號
-p #顯示所有自定義的訊號


trap 'command' signal #自定義指定訊號的處理方式
trap '' signal #忽略指定的訊號
trap '-' signal #恢復訊號預設操作
trap func EXIT #退出時執行func

顯示所有的訊號

root@xu-ubuntu:~/scripts# trap -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
。。。

訊號的3種表示方法

3) SIGQUIT
3          #訊號ID
SIGQUIT    #完整寫法,大小寫都支援
QUIT       #簡短寫法,大小寫都支援
trap "echo '捕捉到了 ctrl+c訊號'" SIGINT
trap "echo '捕捉到了 ctrl+\訊號'" quit
trap -p
for i in {1..15};do
 sleep 1
 echo $i
done

trap '' int
trap -p

for i in {16..30};do
 sleep 1
 echo $i
done

trap '-' int
trap -p

for i in {31..45};do
 sleep 1
 echo $i
done

【2】、建立臨時檔案mktemp

mktemp 命令用於建立並顯示臨時檔案,可避免衝突

#常用選項
-d|--directory          #建立目錄
-u|--dry-run            #只輸出命令,不執行
-p DIR|--tmpdir[=DIR]   #指明臨時檔案所存放目錄位置
-t                      #將檔案儲存到$TMPDIR 定義的目錄中,如果該變數未定義,則儲存到/tmp 目錄中
root@xu-ubuntu:~/scripts# mktemp 
/tmp/tmp.mt31TMmcSK

# 如果指定了檔名,則後面一定要有X,至少要3個,X會被替換成隨機串
root@xu-ubuntu:~/scripts# mktemp XXXX
vrWA
root@xu-ubuntu:~/scripts# ll vrWA 
-rw------- 1 root root 0 Dec  2 10:30 vrWA
root@xu-ubuntu:~/scripts# mktemp testXXXX
testFo7g
root@xu-ubuntu:~/scripts# ll testFo7g 
-rw------- 1 root root 0 Dec  2 10:30 testFo7g
# 賦值給變數
root@xu-ubuntu:~/scripts# file=`mktemp`
root@xu-ubuntu:~/scripts# echo $?
0
root@xu-ubuntu:~/scripts# echo $file
/tmp/tmp.Bz19OE4SHV
root@xu-ubuntu:~/scripts# echo 123 > $file
root@xu-ubuntu:~/scripts# cat $file
123
root@xu-ubuntu:~/scripts# ll $file
-rw------- 1 root root 4 Dec  2 10:31 /tmp/tmp.Bz19OE4SHV
# 建立目錄
root@xu-ubuntu:~/scripts# mktemp -d 
/tmp/tmp.N1GOey7oZo
root@xu-ubuntu:~/scripts# ll /tmp/tmp.N1GOey7oZo -d
drwx------ 2 root root 4096 Dec  2 10:33 /tmp/tmp.N1GOey7oZo/

【3】、安裝複製檔案install

install 功能相當於cp,chmod,chown,chgrp ,mkdir 等相關工具的集合

#常用選項
-m|--mode=MODE          #指定許可權,預設755
-v|--verbose            #顯示過程
-o|--owner=OWNER        #指定屬主
-g|--group=GROUP        #指定屬組
-d|--directory DIR      #指定目錄,如果不存在就建立
#如果目錄不存在,則建立目錄,並將許可權設成777,如果目錄存在,則修改其許可權
root@xu-ubuntu:~/scripts# install -m 777 -d testdir
root@xu-ubuntu:~/scripts# ll testdir/ -d
drwxrwxrwx 2 root root 4096 Dec  2 10:35 testdir/

root@xu-ubuntu:~/scripts# install -m 755 -d testdir
root@xu-ubuntu:~/scripts# ll testdir/ -d
drwxr-xr-x 2 root root 4096 Dec  2 10:35 testdir/



#將檔案複製到指定目錄,並指定屬主屬組,許可權,可以一步完成
root@xu-ubuntu:~/scripts# install -m 777 -o xu -g xu func1.sh /tmp/ 
root@xu-ubuntu:~/scripts# ll /tmp/func1.sh 
-rwxrwxrwx 1 xu xu 483 Dec  2 10:38 /tmp/func1.sh*

六、陣列

【1】、陣列定義

變數:儲存單個元素的記憶體空間

陣列:儲存多個元素的連續的記憶體空間,相當於多個變數的集合

陣列名和索引

  • 索引的編號從0開始,屬於數值索引
  • 索引可支援使用自定義的格式,而不僅是數值格式,即為關聯索引,bash 4.0版本之後開始支援
  • bash的陣列支援稀疏格式(索引不連續)

【2】、陣列宣告

#普通陣列可以不事先宣告,直接使用
declare -a ARRAY_NAME
#關聯陣列必須先宣告,再使用
declare -A ARRAY_NAME

兩者不能相互轉換

【3】、陣列賦值

# 一次只賦值一個元素
root@xu-ubuntu:~/scripts# num[0]=0
root@xu-ubuntu:~/scripts# num[1]=1
root@xu-ubuntu:~/scripts# echo ${num[0]}
0

# 一次性給所有元素賦值
root@xu-ubuntu:~/scripts# title=("ceo" "coo" "cto")
root@xu-ubuntu:~/scripts# echo ${title[1]}
coo
root@xu-ubuntu:~/scripts# echo ${title[2]}
cto

# 間斷賦值
root@xu-ubuntu:~/scripts# week=([0]="sun" [4]="thur")
root@xu-ubuntu:~/scripts# echo ${week[0]}
sun
root@xu-ubuntu:~/scripts# echo ${week[1]}

root@xu-ubuntu:~/scripts# echo ${week[4]}
thur

# 使用read接收賦值
root@xu-ubuntu:~/scripts# read -a  test
a b c d 
root@xu-ubuntu:~/scripts# echo ${test[1]}
b

【4】、顯示所有的陣列

root@xu-ubuntu:~/scripts# declare -a
declare -a BASH_ARGC=([0]="0")
declare -a BASH_ARGV=()
declare -a BASH_COMPLETION_VERSINFO=([0]="2" [1]="11")
declare -a BASH_LINENO=()
declare -a BASH_REMATCH=()
declare -a BASH_SOURCE=()
declare -ar BASH_VERSINFO=([0]="5" [1]="1" [2]="16" [3]="1" [4]="release" [5]="x86_64-pc-linux-gnu")
declare -a DIRSTACK=()
declare -a FUNCNAME
declare -a GROUPS=()
declare -a PIPESTATUS=([0]="0")
declare -a num=([0]="0" [1]="1")
declare -a test=([0]="a" [1]="b" [2]="c" [3]="d")
declare -a title=([0]="ceo" [1]="coo" [2]="cto")
declare -a week=([0]="sun" [4]="thur")

【5】、陣列引用

# 引用特定的陣列元素
#如果省略[INDEX]表示引用下標為0的元素
${ARRAY_NAME[INDEX]}

root@xu-ubuntu:~/scripts# arr=({01..10})
root@xu-ubuntu:~/scripts# echo ${arr[2]}
03
root@xu-ubuntu:~/scripts# echo ${arr[4]}
05
# 如果不加index,預設是輸出第一個值
root@xu-ubuntu:~/scripts# echo ${arr}
01
# 引用陣列所有的元素
root@xu-ubuntu:~/scripts# echo ${arr[*]}
01 02 03 04 05 06 07 08 09 10
root@xu-ubuntu:~/scripts# echo ${arr[@]}
01 02 03 04 05 06 07 08 09 10

# 遍歷陣列
root@xu-ubuntu:~/scripts# arr=({01..05})
root@xu-ubuntu:~/scripts# for i in ${arr[*]};do echo $i ; done
01
02
03
04
05
root@xu-ubuntu:~/scripts# for i in ${arr[@]};do echo $i ; done
01
02
03
04
05

# 取出下標
root@xu-ubuntu:~/scripts# echo ${!arr[*]}
0 1 2 3 4
root@xu-ubuntu:~/scripts# unset arr[2]  # 取消變數
root@xu-ubuntu:~/scripts# echo ${!arr[*]}
0 1 3 4
root@xu-ubuntu:~/scripts# for i in ${!arr[@]};do echo "$i--->${arr[$i]}" ; done
0--->01
1--->02
3--->04
4--->05
# 陣列長度
root@xu-ubuntu:~/scripts# echo ${#arr[*]}
4

【6】、刪除陣列

刪除陣列中的某元素,會導致稀疏格式

unset ARRAY[INDEX]

刪除整個陣列

unset ARRAY

【7】、陣列資料處理

1、陣列切片

root@xu-ubuntu:~/scripts# arr=({a..d})
root@xu-ubuntu:~/scripts# echo ${arr[*]}
a b c d
root@xu-ubuntu:~/scripts# echo ${arr[*]:1:2}
b c

# 從1位置開始取到末尾
root@xu-ubuntu:~/scripts# echo ${arr[*]:1}
b c 

2、陣列追加元素

root@xu-ubuntu:~/scripts# arr[4]=1234
root@xu-ubuntu:~/scripts# echo ${arr[4]}
1234

root@xu-ubuntu:~/scripts# arr[${#arr[*]}]=haha
root@xu-ubuntu:~/scripts# arr[${#arr[*]}]=haha
root@xu-ubuntu:~/scripts# arr[${#arr[*]}]=haha
root@xu-ubuntu:~/scripts# arr[${#arr[*]}]=haha
root@xu-ubuntu:~/scripts# echo ${arr[*]}
a b c d 1234 haha haha haha haha

【8】、關聯陣列

關聯陣列與普通陣列區別:

  • 關聯陣列要先宣告,才能使用,普通陣列可以不用宣告
  • 關聯陣列可以自定義下標,普通陣列必須用數
declare -A ARRAY_NAME 
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)
root@xu-ubuntu:~/scripts# declare -A arr1
root@xu-ubuntu:~/scripts# arr1[name]="xu"
root@xu-ubuntu:~/scripts# arr1[age]=18
root@xu-ubuntu:~/scripts# arr1[gender]=男
root@xu-ubuntu:~/scripts# echo $arr1

root@xu-ubuntu:~/scripts# echo ${arr1[name]}
xu
root@xu-ubuntu:~/scripts# echo ${arr1[gender]}
男
root@xu-ubuntu:~/scripts# declare -A arr2

# 一次性定義完成
root@xu-ubuntu:~/scripts# arr2=([name]="aaa" [age]=18 [gender]="男")
root@xu-ubuntu:~/scripts# echo ${arr2[name]}
aaa

# 取出index
root@xu-ubuntu:~/scripts# echo ${!arr1[*]}
gender age name
root@xu-ubuntu:~/scripts# echo ${!arr2[*]}
gender age name
# 單獨取出index是,順序是不一定的,也就是說如果我們根據index遍歷關聯陣列的結果順序不是按照我們定義時的去輸出
root@xu-ubuntu:~/scripts# for i in ${!arr2[*]} ;do echo "$i---${arr2[$i]}" ;done
gender---男
age---18
name---aaa

【9】、範例

# 生成10個隨機數儲存於陣列中,並找出其最大值和最小值
function SORT(){
    temp=0
    for j in ${!arr[*]}
    do
        let k=$j+1
        for k  in ${!arr[*]}
        do
            if [ ${arr[$j]} -gt ${arr[$k]} ];then
                temp=${arr[$j]}
                arr[$j]=${arr[$k]}
                arr[$k]=$temp
            fi
        done
    done
    echo "max in arr is ${arr[1]}"
    echo "min in arr is ${arr[$j]}"
}

for i in `seq 10`
do
    arr[$i]=$RANDOM
done
echo "arr is ${arr[*]}"
SORT arr

七、字串處理

【1】、字串切片

1、基於偏移量取字串

# #返回字串變數var的字元的長度,一個漢字算一個字元
root@xu-ubuntu:~/scripts# echo ${#str}
6

root@xu-ubuntu:~/scripts# str=123456
#返回字串變數var中從第offset個字元後(不包括第offset個字元)的字元開始,到最後的部分,offset的取值在0 到 ${#var}-1 之間(bash4.2後,允許為負值)
root@xu-ubuntu:~/scripts# echo ${str:2}
3456
root@xu-ubuntu:~/scripts# echo ${str:2:2}
34

#取字串的最右側幾個字元, 注意:冒號後必須有一空白字元
root@xu-ubuntu:~/scripts# echo ${str: -2}
56
root@xu-ubuntu:~/scripts# echo ${str: 2: -2}
34

2、基於模式取子串

#其中word可以是指定的任意字元,自左而右,查詢var變數所儲存的字串中,第一次出現的word, 刪除字串開頭至第一次出現word字串(含)之間的所有字元,即懶惰模式,以第一個word為界刪左留右
${var#*word}
#從var變數的值中刪除以word開頭的部分
${var#word}
#同上,貪婪模式,不同的是,刪除的是字串開頭至最後一次由word指定的字元之間的所有內容,即貪婪模式,以最後一個word為界刪左留右
${var##*word}
${var##word}
root@xu-ubuntu:~/scripts# echo $str
abcd1234abcd1234
root@xu-ubuntu:~/scripts# echo ${str#abc}
d1234abcd1234
root@xu-ubuntu:~/scripts# echo ${str#*abc}
d1234abcd1234
root@xu-ubuntu:~/scripts# echo ${str#*cd}
1234abcd1234

# 貪婪匹配
root@xu-ubuntu:~/scripts# echo ${str##*cd}
1234
root@xu-ubuntu:~/scripts# echo ${str##*abcd}
1234
#其中word可以是指定的任意字元,功能:自右而左,查詢var變數所儲存的字串中,第一次出現的word, 刪除字串最後一個字元向左至第一次出現word字串(含)之間的所有字元,即懶惰模式,以從右向左的第一個word為界刪右留左

${var%word*}
${var%word}

#同上,只不過刪除字串最右側的字元向左至最後一次出現word字元之間的所有字元,即貪婪模式,以從右向左的最後一個word為界刪右留左
${var%%word*}
${var%%word}
[root@web01 ~]# str=abcd1234abcd12345
[root@web01 ~]# echo $str
abcd1234abcd12345
[root@web01 ~]# echo ${str%abc*}
abcd1234
[root@web01 ~]# echo ${str%345}
abcd1234abcd12
[root@web01 ~]# echo ${str%%34}
abcd1234abcd12345
[root@web01 ~]# echo ${str%%34*}
abcd12

【2】、查詢替換刪除

#查詢var所表示的字串中,第一次被pattern所匹配到的字串,以substr替換之,懶惰模式
${var/pattern/substr}
#查詢var所表示的字串中,所有能被pattern所匹配到的字串,以substr替換之,貪婪模式
${var//pattern/substr}
#查詢var所表示的字串中,行首被pattern所匹配到的字串,以substr替換之
${var/#pattern/substr}
#查詢var所表示的字串中,行尾被pattern所匹配到的字串,以substr替換之
${var/%pattern/substr}
[root@ubuntu2204 ~]# str=abcd1234abcd12345
#從左到右,替換第一個123為XYZ
[root@ubuntu2204 ~]# echo ${str/123/XYZ}
abcdXYZ4abcd12345
#從左到右,替換所有123為XYZ
[root@ubuntu2204 ~]# echo ${str//123/XYZ}
abcdXYZ4abcdXYZ45
#替換行首的abc為XYZ
[root@ubuntu2204 ~]# echo ${str/#abc/XYZ}
XYZd1234abcd12345
#替換行尾的12345為XYZ
[root@ubuntu2204 ~]# echo ${str/%12345/XYZ}
abcd1234abcdXYZ
# 刪除就是將查詢到的替換為空
#刪除var表示的字串中第一次被pattern匹配到的字串,懶惰模式
${var/pattern}
#刪除var表示的字串中所有被pattern匹配到的字串,貪婪模式
${var//pattern}
#刪除var表示的字串中所有以pattern為行首匹配到的字串
${var/#pattern}
#刪除var所表示的字串中所有以pattern為行尾所匹配到的字串
${var/%pattern}
[root@ubuntu2204 ~]# str=abcd1234abcd12345
#從左到右刪除第一個1234
[root@ubuntu2204 ~]# echo ${str/1234}
abcdabcd12345
#從左到右刪除所有1234
[root@ubuntu2204 ~]# echo ${str//1234}
abcdabcd5
#刪除行首abcd
[root@ubuntu2204 ~]# echo ${str/#abcd}
1234abcd12345
#刪除行尾12345
[root@ubuntu2204 ~]# echo ${str/%12345}
abcd1234abcd

【3】、字元大小寫轉換

#把var中的所有小寫字母轉換為大寫
${var^^}
#把var中的所有大寫字母轉換為小寫
${var,,}
[root@web01 ~]# str=abcd1234ABCD12345
[root@web01 ~]# echo ${str^^}
ABCD1234ABCD12345
[root@web01 ~]# echo ${str,,}
abcd1234abcd12345

相關文章