優雅編寫shell腳本


shell的重要性不用說,不管是運維還是開發都需要知道如何編寫它,下面就跟着我一起慢慢深入了解shell的世界,具體參照(https://tldp.org/LDP/abs/html/)

第一部分 熱身

從sha-bang(#!)開始,先上一個小例子

1.1 清除/var/log 下的 log 文件
#!/bin/bash
#Cleanup
#當然要使用root身份來運行這個腳本
cd /var/log
cat /dev/null > messages
cat /dev/null > wtmp
echo "Log files cleaned up."

這根本就沒什么稀奇的, 只不過是命令的堆積

1.2 一個改良版 清除/var/log 下的 log 文件
#!/bin/bash
#一個 Bash 腳本的正確的開頭部分. 3
#Cleanup, 版本 2
#當然要使用 root 身份來運行.
#在此處插入代碼,來打印錯誤消息,並且在不是 root 身份的時候退
LOG_DIR=/var/log
#如果使用變量,當然比把代碼寫死的好.
cd $LOG_DIR
cat /dev/null > messages
cat /dev/null > wtmp
echo "Logs cleaned up." 18
exit # 這個命令是一種正確並且合適的退出腳本的方法.

這個腳本看上去像一個完整的shell腳本

1.3 一個增強和廣義的刪除logfile文件
#!/bin/bash
#Cleanup, version 3
LOG_DIR=/var/log
ROOT_UID=0     # $UID 為 0 的時候,用戶才具有根用戶的權限
LINES=50       # 默認保存行數
E_XCD=86       # 不能修改目錄
E_NOTROOT=87   # 非root執行將以87返回

if [ "$UID" -ne "$ROOT_UID" ] #root用戶判斷,不等於0則退出返回$E_NOTROOT
then
  echo "Must be root to run this script."
  exit $E_NOTROOT
fi  

if [ -n "$1" ] #測試是否有命令行參數(非空)
then
  lines=$1
else  
  lines=$LINES #默認
fi  
cd $LOG_DIR
if [ `pwd` != "$LOG_DIR" ]  # or   if [ "$PWD" != "$LOG_DIR" ]
then
  echo "Can't change to $LOG_DIR."
  exit $E_XCD
fi  
#更有效的方法是
#cd /var/log || {
#echo "Cannot change to necessary directory." >&2
#exit $E_XCD;
#}
tail -n $lines messages > mesg.temp  
mv mesg.temp messages               
echo "Log files cleaned up."
exit 0 #腳本退出前返回成功0

第二部分 基本語法

2.1 特殊字符
字符 說明
# 注釋,行首以#開頭為注釋(#!是個例外)
; 命令分隔符,可以用來在一行中寫多個命令;echo hello;echo there
;; 終止“case”選項
. .命令等價於 source 命令;做為隱藏文件名的一部分;字符匹配,匹配任何的單個字符
" 部分引用."STRING"阻止了一部分特殊字符
' 全引用. 'STRING' 阻止了全部特殊字符
\ 轉義字符
` 后置引用,`pwd`
: 1.空命令,如:"while :" 2.占位符; 如 ": ${username=whoami}" 參數替換,如果多個變量中有一個沒有設置,就打印錯誤信息,如:": ${HOSTNAME?} ${USER?} ${MAIL?}"
$ 在正則表達式中作為行結束符.
${} 參數替換
$*,$@ 位置參數
$? 退出狀態變量.$?保存一個命令/一個函數或者腳本本身的退出狀態.
$$ 進程 ID 變量.這個$$變量保存運行腳本進程 ID
() 命令組.如:1 (a=hello;echo $a)
{xxx,yyy,zzz...} 大括號擴展 cat {file1,file2,file3} > combined_file ;拷貝"file22.txt" 到"file22.backup"中 cp file22.{txt,backup}
{a..z} echo {0..3} && echo {a..c} # 0 1 2 3 a b c
{} 代碼塊 a=123{ a=321; } echo "a = $a" # a = 321
{}& 子shell后台執行 {ping -c1 192.168.1.200 &>/dev/null }&
#!/bin/bash
#Reading lines in /etc/fstab.
File=/etc/fstab
{
read line1
read line2
} < $File
echo "First line in $File is:"
echo "$line1"
echo
echo "Second line in $File is:"
echo "$line2"
exit 0

2.2 變量和參數

2.2.1 變量替換

$ 變量替換操作符
在""中還是會發生變量替換,這被叫做部分引用,或叫弱引用.而在''中就不會發生變 量替換,這叫做全引用,也叫強引用
注意 $var 與 ${var}的區別,不加{}在某些上下文可能發生錯誤,建議使用${var}這種格式變量引用

a=375
he=$a
注意:賦值左右一定不要有空格
hello="A B  C   D"
echo $hello   # A B C D
echo "$hello" # A B  C   D
#引用一個變量將保留其中的空白

2.2.2 變量賦值

= 賦值操作符(前后都不能有空白),不要與-eq 混淆,那個是 test,並不是賦值.

a=879
echo "The value of \"a\" is $a."
let a=16+5 #let賦值

a=`ls -l`          
echo $a           
echo
echo "$a"    #變量引用,保留了空白

2.2.3 特殊的變量類型

Local variables 局部變量 使用local聲明,僅在代碼塊中可以使用的變量,在函數中,僅在該功能代碼塊有意義

func ()
{
  local loc_var=23      
  echo "\"loc_var\" in function = $loc_var"
  global_var=999          
  echo "\"global_var\" in function = $global_var"
}  
func
echo "\"loc_var\" outside function = $loc_var". #輸出是空
echo "\"global_var\" outside function = $global_var" # 999

Environmental variables 全局變量,直接定義a=hello即可
Positional parameters 位置參數
$1..$9 代表腳本后參數列表

 if [ -n "${10}" ]
 then
 echo "Parameter #10 is ${10}"
 fi
{}標記法是一種很好的使用位置參數的方法.這也需要間接引用 args=$# 位置參數的個數
lastarg=${!args} #輸出最后一個參數

shift命令用於對參數的移動(左移),通常用於在不知道傳入參數個數的情況下依次遍歷每個參數然后進行相應處理

sum=0
until [ $# -eq 0 ]
do
        let sum+=$1
        shift 1
done
echo "sum:${sum}"


while [[ $# != 0 ]]; do
        let sum+=$1
        shift 1
done

echo "sum:${sum}"
2.3 引用變量

在一個雙引號中直接使用變量名,一般都是沒有問題的.它阻止了所有在引號中的特殊字符的重新解釋,使用""來防止單詞分割.[4]如果在參數列表中使用雙引號,將使得雙引號中的參數作為一個參 數.即使雙引號中的字符串包含多個單詞(也就是包含空白部分),也不會變為多個參數

List="one two three"
for a in $List   #空白出拆分變量
do
  echo "$a"
done
echo "---"
for a in "$List"   #保留空白
do 
  echo "$a"
done
#輸出結果 
one
two
three
---
one two three
2.4 退出和退出狀態

exit 命令被用來結束腳本,就像 C 語言一樣.他也會返回一個值來傳給父進程,父進程會判斷是否 可用.
每個命令都會返回一個 exit 狀態(有時候也叫 return 狀態).成功返回 0,如果返回一個非 0 值,通 常情況下都會被認為是一個錯誤碼.一個編寫良好的 UNIX 命令,程序,和工具都會返回一個 0 作為 退出碼來表示成功,雖然偶爾也會有例外.
同樣的,腳本中的函數和腳本本身都會返回退出狀態.在腳本或者是腳本函數中執行的最后的命 令會決定退出狀態.在腳本中,exit nnn 命令將會把 nnn 退出碼傳遞給 shell(nnn 必須是 10 進制數 0-255)

 exit $?
 exit 0
2.5 Test結構
  • 一個 if/then 結構可以測試命令的返回值是否為 0(因為0表示成功)如果是的話,執行更多命令.

  • 有一個專用命令"["(左中括號,特殊字符).這個命令與 test 命令等價,但是出於效率上的考慮, 它是一個內建命令.這個命令把它的參數作為比較表達式或是文件測試,並且根據比較的結果, 返回一個退出碼.

  • 還有一個新的[[...]]擴展 test 命令,注意"[["是一個關鍵字,並不是一個命令.

  • Bash 把[[ $a -lt $b ]]看作一個單獨的元素,並且返回一個退出碼.

  • (())和let...也能返回一個退出碼

(( 0 && 1 ))                  
echo $?  # 1  
let "num = (( 0 && 1 ))"
echo $num   # 0
let "num = (( 0 && 1 ))"
echo $?     # 1  


(( 200 || 11 ))             
echo $?     # 0 
let "num = (( 200 || 11 ))"
echo $num   # 1
let "num = (( 200 || 11 ))"
echo $?     # 0     ***
(( 200 | 11 ))              
echo $?    # 0    
#...
let "num = (( 200 | 11 ))"
echo $num   # 203
let "num = (( 200 | 11 ))"
echo $?    # 0     
#兩種test寫法
file=/etc/passwd
if [[ -e $file ]]
then
  echo "Password file exists."
fi

decimal=15
octal=017   # = 15 (decimal)
hex=0x0f    # = 15 (decimal)

if [ "$decimal" -eq "$octal" ]
then
  echo "$decimal equals $octal"
else
  echo "$decimal is not equal to $octal" 
fi  

if [[ "$decimal" -eq "$octal" ]]
then
  echo "$decimal equals $octal"            
else
  echo "$decimal is not equal to $octal"
fi      
  

#簡單表達式寫法
var1=20
var2=22
[ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2"
home=/home/bozo
[ -d "$home" ] || echo "$home directory does not exist."

第三部分 基本之上

3.1內部變量

$PWD

$SECONDS

$UID

3.1聲明變量

declare

第四部分 高級

#[ -f " /root/.ssh/id_rsa.pub" ] || ssh-keygen  -P "" -f /root/.ssh/id_rsa
IpList=("192.168.1.101" "192.168.1.102")
password="san123123123"
for ip in ${IpList[@]}
do
	
	/usr/bin/expect<<-EOF                           
	spawn ssh-copy-id -f ${ip}
	expect {
	"(yes/no)"
	{send "yes\r";exp_continue}
	"*password"
	{send "$password\r"}
	}
	expect eof           
	EOF

done

未完待續。。。。。。。

更多精彩關注公眾號“51運維com” 個人博客


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM