Shell 编程

调试

bash -[选项] xxx.sh

其中选项为:

-x

【功能】: 提供跟踪执行信息,将执行脚本的过程中把实际执行的每个命令显示出来,行首显示+,+后面显示经过替换之后的命令行内容,有助于分析实际执行的是什么命令。

【场合】: 是调试Shell脚本的强有力工具,是Shell脚本首选的调试手段

-v

【功能】: 区别于 -x 参数,-v 选项打印命令行的原始内容,-x 参数打印出经过替换后命令行的内容

【场合】: 仅想显示脚本的内容时

【示例 】 : bash -v script.sh

变量

shell 函数里的变量默认是全局变量,这与 C++ 等语言不同

要用局部变量得用 local 修饰

# !/bin/bash

“# !/bin/bash” 用来告诉操作系统,执行该脚本使用的解释器名字。

#!/bin/bash
# 上面的 “# !/bin/bash” 用来告诉操作系统,执行该脚本使用的解释器名字
echo "Hello world!"
echo "
        I
        Love
        China
"

赋值

a=z                 # 将字符 'z' 赋值给变量 a
b="a string"        # 将字符串赋值给 b
b2=a_string         # 如果不加引号,就不能有空格
c="a string and $b" # 变量在 shell 里就是宏展开
d="a string and $c" # 继续宏展开
e=$(ls -a)          # 命令的结果
f=$((5 * 5))        # 算术扩展
let "f2 = $f + $f"    # let 关键字用于数值赋值,类似(())
g="\t\ta string\n"  # 转义字符

dir_name="tt"
# 创建文件夹 ./tt1,花括号确定变量的范围。
# 当然也可以去掉花括号,如果去掉,那么会把 dir_name1(而不是 dir_name)当做变量,而 dir_name1 未定义。
mkdir -p ${dir_name}1   
ls
sleep 3                 # 延迟 3 秒
rm -r ${dir_name}1
ls

unset

unset可以删除一个变量。删除后,变量不能再次使用。不可以删除只读变量。

name=hahaha
echo "name = $name"
unset name 
echo "name = $name"
#输出
#name = hahahaha
#name = 

只读变量

readonly R0=100
R0=200
echo $?   # 上一条指令是错误的,所以 $? 为非0
#输出
#bash: R0:只读变量 #这是报错,因为修改了只读变量的值
#1 报错之后,返回的值不为0,默认返回1,所以用echo $?打印1

转义与引用

Shell 中一共有 4 中引用符,分别是 双引号,单引号,反引号,转义符

# 转义
# 跟其他编程语言里的转义一样,使用转义符 \
echo \# 使用转义输出注释符号 \#
Dollar=123
echo \$Dollar is $Dollar
echo 8 \* 8 = 64

# 引用
# Shell 中一共有 4 中引用符,分别是 双引号,单引号,反引号,转义符

# "" 双引号:部分引用,可以解释变量
echo "\$Dollar is $Dollar"
# 带不带双引号看起来一样,但是对于输出空格有区别
VAR="A     B      C"
echo 不带引号对于连续空格只输出一个:$VAR # 输出 A B C
echo "带引号会把所有空格输出:$VAR" # 输出 A     B      C

# '' 单引号:全引用,只按照字面意思输出内容,转义符也不能用了
echo '$Dollar 在单引号内还是 $Dollar。'
echo '转义符在单引号内输出 \,单引号只把内容作为字面量输出'
echo '转义符不能用,单引号内不能输出单引号'

# `` 反引号:命令替换,将命令的标准输出作为值赋给某个变量
# 命令替换也可以使用 $(命令) 的形式
LS=`ls`
echo "=== LS ==="
echo $LS
echo "=== LS ==="
LSA=$(ls -a)
echo $LSA
# $() 支持嵌套
$(echo $(ls) > tmp.sh)
TMP=$(cat tmp.sh)
echo === tmp ===
echo $TMP
echo === tmp ===
rm tmp.sh

流程控制

相等判断符=、== 和 -eq的区别和使用场景

在Shell脚本中,有单等号“=”、双等号“==”和“-eq”共3种相等判断符。在shell脚本中,单等号和双等号属于算数运算符;“-eq”属于关系运算符。其使用场景限制:
(1)单等号和双等号能用于字符string类型和整型integer的相等判断。
(2)相等关系运算符“-eq”仅能用于整型integer的相等比较。
(3)相等关系运算符“-eq”不能在算数运算表达式“(( ))”中
(4)在条件表达式“[ ]”中,单等号和双等号等价,都是相等算数运算符。
(5)在算数运算表达式“(( ))”中,单等号是赋值算数运算符,双等号为相等算数运算符。

总结:在进行相等判断时,最通用的方法是使用双等号“==”相等判断符。

if

#!/bin/bash
# bash 的判断与循环与其他语言类似,有 if else elif case

# if 判断结构
# if expression; then
#    command
# elif expression; then
#    command
# else
#    command
# fi
if [ ! -e tmp.sh ]; then
  echo "tmp.sh 不存在,创建它"
  touch tmp.sh
  if [ -e tmp.sh ]; then
    echo "tmp.sh 创建好了"
  else
    echo "tmp.sh 创建失败"
  fi
else
  echo "tmp.sh 存在,删了它"
  rm tmp.sh
fi

test命令返回值与 if 判断的一些区别

test命令结果只是返回命令执行成功还是失败,而不是真值判断。

[ 1 -eq 1 ]
echo '[ 1 -eq 1 ]' $?  # 0。打印$?只是表示test命令[ 1 -eq 1 ]是否其结果是正确还是false。因为是正确的所以返回0。这与C++等编程语言不一样。
if [ 1 -eq 1 ]; then
    echo "equal" # 满足 if 条件,打印 equal。
else
    echo "not equal"
fi

test 命令

# 测试结构
# 1. test expression   使用 test 指令
# 2. [expression]      使用 []

# 文件测试
# 1. test file_operator FILE
# 2. [file_operator FILE]
文件测试
# 文件测试符,文件不存在时,均返回假
# -b FILE 当文件存在且是块文件时,返回真,否则为假
# -c FILE 当文件存在且是设备文件时,返回真,否则为假
# -d FILE 测试文件是否为目录
# -e FILE 测试文件或目录是否存在
# -f FILE 测试文件是否为普通文件
# -x FILE 判断文件是否为可执行文件
# -w FILE 判断文件可写
# -r FILE 判断文件可读
# -l FILE 判断是否为链接文件
# -p FILE 判断是否为管道文件
# -s FILE 判断文件存在且大小不为 0
# -S FILE 判断是否为 socket 文件
# -g FILE 判断文件是否设置了 SGID
# -u FILE 判断文件是否设置了 SUID
# -k FILE 判断文件是否设置了 sticky 属性
# -G FILE 判断文件属于有效的用户组
# -O FILE 判断文件属于有效的用户
# FILE1 -nt FILE2 FILE1 比 FILE2 新时返回真
# FILE1 -ot FILE2 FILE1 比 FILE2 旧时返回真
# 如果目录不存在就创建。在linux下目录也是文件
if [ ! -d $result_name ];then
   mkdir -p $result_name
fi
字符串测试
# 字符串测试
# 主要包括 等于、不等于、大于、小于、是否为空
# -z string 为空时返回真
echo "字符串测试"
[ -z "" ]
echo '[ -z "" ]' $?   # 结果 0,表示为真

# -n string 非空时返回真
[ -n "aaa" ]
echo '[ -n "aaa" ]' $? # 0
[ "string1" = "string2" ]
echo '[ "string1" = "string2" ]' $? # 1
[ "string1" != "string2" ]
echo '[ "string1" != "string2" ]' $? # 0
[ "string1" > "string2" ]
echo '[ "string1" > "string2" ]' $?  # 0
[ "string1" < "string2" ]
echo '[ "string1" < "string2" ]' $? # 0
整数比较
# 整数比较
# -eq 意 相等
# -gt 意 >
# -lt 意 <
# -ge 意 >=
# -le 意 <=
# -ne 意 !=
[ 1 -eq 2 ]
echo '[ 1 -eq 2 ]' $? # 1
[ 1 -gt 2 ]
echo '[ 1 -gt 2 ]' $? # 1
[ 1 -lt 2 ]
echo '[ 1 -lt 2 ]' $? # 0
[ 1 -ge 2 ]
echo '[ 1 -ge 2 ]' $? # 1
[ 1 -le 2 ]
echo '[ 1 -le 2 ]' $? # 0
[ 1 -ne 2 ]
echo '[ 1 -ne 2 ]' $? # 0

if (( 1 < 2 )); then # 注意双层圆括号,因为 (())用于执行算数真值测试
    echo "less than"
else
    echo "greater then"
fi
# 等价于
if [ 1 -lt 2 ]; then
    echo "less than"
else
    echo "greater then"
fi
逻辑测试运算符
# 逻辑测试符与逻辑运算符
# ! expression 取反
# expression -a expression 同为真,结果为真        
# expression -o expression 只要有一个为真,结果为真
touch tmp.sh
[ ! -e tmp.sh ]
echo '[ ! -e tmp.sh ]' $? # 1

[ -e tmp.sh -a -e tmp1.sh ] 
echo '[ -e tmp.sh -a -e tmp1.sh ]' $? # 1

[ -e tmp.sh -o -e tmp1.sh ]
echo '[ -e tmp.sh -o -e tmp1.sh ]' $? # 0

# -a -o 可以用 && 和 || 替代,不过写法上会有区别
[ -e tmp.sh ] && [ -e tmp1.sh ]
echo '[ -e tmp.sh ] && [ -e tmp1.sh ]' $? # 1

[ -e tmp.sh ] || [ -e tmp1.sh ]
echo '[ -e tmp.sh ] || [ -e tmp1.sh ]' $? # 0

case

# case 判断结构
# case VAR in
# var1) command ;;
# var2) command ;;
# ...
# *) command ;;
# esac

# 执行时找到第一个匹配的分支并执行相应的命令,然后直接跳到esac之后,不需要像C语言一样用break跳出。

read -p "请输入数字:" NUM
case $NUM in
1) echo "输入为 1" ;;
2) echo "输入为 2" ;;
*) echo "输入为 其他" ;;
esac

循环

循环控制,break continue,与其他编程语言一致。

for
for in
# for 循环
# 带列表的 for 循环:
# for VAR in (list)
# do
#   command
# done
for NUMBER in 1 2 3 4 5
do
  echo $NUMBER
done

fruits="apple banana orange"
for FRUIT in ${fruits}
do
  echo $FRUIT
done

# 循环数字时可以使用 {a..b} 表示从 a 循环到 b
for N in {2..10}
do
  echo $N
done
# 其中 {2..10} 可以用 seq 命令替换
echo "echo with seq:"
for N in $(seq 2 10)
do
  echo $N
done
# seq 命令可以加 “步长”
for N in $(seq 1 2 20)
do
  echo $N
done

# 可以看出,for in 后面的内容可以是任意命令的标准输出
# 比如,我们可以输出当前目录下的所有带 sh 的文件
for VAR in $(ls | grep sh)
do
  echo $VAR
done

# 如果 for 后面没有 in ,则相当于是 in $@
# 你可以执行 bash 07_循环.sh a b c 试一试。
# $@:表示脚本参数的内容(即 a b c)
# $#:表示脚本参数的个数(即3)。
for VAR
do
  echo $VAR
done
类C的for循环
echo "类 C 的 for 循环"
# for ((exp1; exp2; exp3))
# do
#   command
# done
for ((i=0, j=100; i < 10; ++i)) # ++i 与 i++ 一样
do
  echo $i $j
done
while
echo "while 循环"
# 语法如下:
# while expression
# do
#   command
# done
# while ((1)) 会无限循环
COUNT=0
while [ $COUNT -lt 5 ]
do
  echo $COUNT
  let "COUNT++"
done

while 按行读取文件

# while 按行读取文件
echo "john  30  boy
sue   20  girl" > tmp.txt
while read LINE
do
  NAME=`echo $LINE | awk '{print $1}'`
  AGE=`echo $LINE | awk '{print $2}'`
  SEX=`echo $LINE | awk '{print $3}'`
  echo $NAME $AGE $SEX
done < tmp.txt    # 输入重定向
rm tmp.txt
until

until 与 while 类似,区别在于 until 判断为否,会继续循环,而 while 判断为真,才继续循环

until ((0)) 会无限循环


COUNT=0
until [ $COUNT -gt 5 ]
do
  echo $COUNT
  let "COUNT++"
done
select

select 是一种菜单式的循环方式,语法结构与 for 相似,每次循环的值由用户选择。

注意是选择编号。

echo "choose your menu:"
select MENU in "apple" "banana" "orange" "exit"
do
  echo "you choose $MENU"
  if [[ $MENU == "exit" ]]
  then
    break
  else
    echo "choose again"
  fi
done

打印
choose your menu:
1) apple
2) banana
3) orange
4) exit
#? 1
you choose apple
choose again
#? 4
you choose exit

函数

# 函数的定义
function FUNCTION_NAME() {
  command
}
省略 function 关键字
FUNCTION_NAME() {
  command
}  

当然 function关键字可以省略。如果有了function关键字,用sh运行脚本会出错:function: not found。用bash没有问题。

# 函数参数
# 与脚本的参数使用一致
func4 () {
  echo "第一个参数 $1"
  echo "第二个参数 $2"
  echo "所有参数 $@"
  echo "参数数量 $#"
}
func4 a b c

打印
第一个参数 a
第二个参数 b
所有参数 a b c
参数数量 3
pow() {
  let "r=$1**$2"
  return $r

}
pow 2 5
echo $? # 32 = 2^5

数组

bash 只支持一维数组。Shell 数组元素的下标由0开始。

赋值

declare -a mArray
mArray[0]="nihao"
mArray[1]=2
# 定义时赋值,数组的元素用空格分开,其他字符会被当成值,比如 "php", 会被当成 php,
declare -a mArray=("php" "python" 123)
# mArray=("php" "python" 123) # 数组声明也可以忽略 declear -a

# 数组取值,需要用 ${数组名[索引]} 语法
echo ${mArray[0]} # php
echo ${mArray[1]} # python
echo ${mArray[2]} # 123
# 使用 @ * 可以索引全部元素
# @ 得到以空格分开的元素值
# * 得到整个字符串
echo ${mArray[@]} # php python 123
echo ${mArray[*]} # php python 123

数组长度

# 数组长度
echo "数组长度是 ${#mArray[@]}" # 数组长度是 3
echo "数组长度是 ${#mArray[*]}" # 数组长度是 3

截取与合并

# 数组截取
# 可以获取子数组,下面示例为获取数组的第 1、2 下标位置的元素
echo ${mArray[@]: 1:2} # python 123
# 可以获取数组中某个元素的若干字符,下面示例为获取数组中第二个元素的 从0开始 3个字符
echo ${mArray[1]: 0:3} # pyt

# 合并数组
Front=("javascript" "typescript")
Conn=(${mArray[@]} ${Front[@]})
echo ${Conn[@]} # php python 123 javascript typescript
# 合并得到数组的长度
echo ${#Conn[@]} # 5       

取消数组元素

需要注意的是,数组被取消位置的元素变为了空,而不是后面的元素向前移动。

# 取消数组或元素
echo "原始数组:${mArray[@]}"
unset mArray[1]
echo "取消下标为 1 的元素后,数组为:${mArray[@]},数组长度为 ${#mArray[@]}"
# 需要注意的是,数组的 1 位置的元素变为了空,而不是后面的元素向前移动
echo "数组 0 位置的元素为 ${mArray[0]}, 
数组 1 位置的元素为 ${mArray[1]}, 
2 位置的元素为 ${mArray[2]}"

#打印
原始数组:php python 123
取消下标为 1 的元素后,数组为:php 123,数组长度为 2
数组 0 位置的元素为 php, 
数组 1 位置的元素为 , 
2 位置的元素为 123

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部