Shell进阶脚本70个练习

目录

1.数组的基本脚本

2.子shell与父shell

3.子shell与父shell进阶

4.基础函数

5.函数进阶之作用域域返回值

6.进程控制与文件描述和管道

7.排序算法

8.花阔号的使用

9.波浪号

10.变量替换

11.高级变量替换

12.命令替换

13.算术替换

14.进程替换

15.单词切割

16.路径替换

17.随机获取密码

18.shell解释器和属性

19.初始化命令shopt终端与tput终端

20.trap信号捕捉

21.shell脚本内容排错与进度条

22.xargs参数传参

23.shift移动参数

24.其他


1.数组的基本脚本

#!/bin/bash
name[0]="jacob"       # 定义一个数组
name[1]="Rose"        # 附加数组
name[2]="Rick"        # 附加数组
name[3+3]="TinTin"    # 追加数组,相当与在第6位追加
echo ${name[0]}       # 打印数组第一个内容
echo ${name[1]}       # 打印数组第二个内容
echo ${name[6]}       # 打印数组第四个内容或echo ${name[3+3]}
name[3+3]="Tin"       # 如果有值则是修改
echo ${name[*]}       # 打印数组所有值,会把值当成一个整体
echo ${name[-1]}      # 打印数组最后一个值
echo ${name[-2]}      # 打印数组最后第二个值
echo ${#name[*]}      # 打印数组内的值的个数
echo ${name[@]}       # 打印数组所有值,分为单个个体
i=1
addr[$i]="shanghai"   # 变量名不能用变量定义,但数组内的索引可以
​
name1=(jacob Rose Rick TinTin)  # 数组还可以用这样定义
echo ${!name1[*]}     # 获取所有数组的索引或echo ${!name1[@]} 
​
root=$(df / |tail -n +2)  # 定义一个命令数组变量
echo ${root[*]}       # 打印该命令数组所有值
echo ${root[1]}       # 打印该命令数组第二个值

# 数组进阶---------------关联数组
#!/bin/bash
daclare -A man    # 声明数组是关联数组
# 定义关联数组
man[name]=TOM
man[age]=23
man[addr]=shangzheng
man[phon]=12345678987
​
echo ${man[*]}    # 打印所有关联数组的值
​
woman=([name]=lisi [age]=25 [addr]=shaoyan [phon]=98765432123)  # 或者可以这么>定义
unset woman[age]  # 删除数组内的某个键
unset woman       # 删除数组


###--------------脚本1------------------
#!/bin/bash
# 遍历数组内容
name=(zhansan lisi wanger fudo)
for i in "${name[@]}"
do
    echo $i
done
​
###--------------脚本2------------------
#!/bin/bash
# 遍历数组下标,并根据小标打印出数组的值
for i in ${!name[@]}
do
    echo ${name[i]}
done
​
###--------------脚本3------------------
#!/bin/bash
# 遍历数组下标,用while循环的方式循环
i=0
while [ $i -le ${#name[@]} ]
do
    echo ${name[i]}
    let i++
done
​
###--------------脚本4------------------
#!/bin/bash
# 取出关联数组的值
declare -A woman
woman=([name]=zhangsan [age]=45 [addr]=shan [phon]=1111111111)
# echo ${!woman[*]}    # 此时取出的是关联数组名,值不会取出
for i in ${!woman[@]}
do
    echo ${woman[$i]}
done
​

2.子shell与父shell

###--------------脚本1------------------###
#!/bin/bash
hi="hello"
echo "++++++++++++++++++++++++++++"
echo "+        我是父shell       +"
echo "++++++++++++++++++++++++++++"
echo "PWD=$PWD"
echo "bash_subshll=$BASH_SUBSHELL"     # 当前父shell是0
​
# 通过()开启子shell
(
sub_hi="I am a subshell"
echo -e "\t+++++++++++++++++++++++"
echo -e "\t    进入子shell       +"
echo -e "\t+++++++++++++++++++++++"
echo -e "\tPWD=$PWD"
echo -e "\tbash_subshll=$BASH_SUBSHELL" # 当前子shell是1
echo -e "\thi=$hi"
echo -e "\tsub_hi=$sub_hi"              # 此时在子shell中定义的变量可以打印
cd /etc;echo -e "\tPWD=$PWD"
)
​
echo "++++++++++++++++++++++++++++"
echo "+        返回父shell       +"
echo "++++++++++++++++++++++++++++"
echo "PWD=$PWD"
echo "hi=$hi"
echo "sub_hi=$sub_hi"                   # 此时在子shell中定义的变量不可以打印
echo "bash_subshll=$BASH_SUBSHELL"      # 返回父shell就是0
​
###--------------脚本2------------------###
#!/bin/bash
hi="hello"
echo "++++++++++++++++++++++++++++"
echo "+        我是父shell       +"
echo "++++++++++++++++++++++++++++"
echo "bash_subshll=$BASH_SUBSHELL" 
# 通过()开启子shell
(
echo -e "\t+++++++++++++++++++++++"
echo -e "\t+    进入子shell       +"
echo -e "\t+++++++++++++++++++++++"
echo -e "\tbash_subshll=$BASH_SUBSHELL"
     (
      echo -e "\t\t+++++++++++++++++++++++"
      echo -e "\t\t+    进入子shell       +"
      echo -e "\t\t+++++++++++++++++++++++"
echo -e "\t\tbash_subshll=$BASH_SUBSHELL"
pstree | grep subshell
      )
)
echo "++++++++++++++++++++++++++++"
echo "+        返回父shell       +"
echo "++++++++++++++++++++++++++++"
echo "bash_subshll=$BASH_SUBSHELL"
​
​
###--------------脚本3------------------###
#!/bin/bash
# 使用管道开启子shell
sum=0
df | grep "^/"|while read name total used free other
do
    echo "free=$free"
    let sum+=free  
    echo "sum=$sum"
done
echo $sum
# 注意此时如果是0的话就是错误
​
###--------------脚本4------------------###
# 如果不想起子shell则不要用管道
#!/bin/bash
tmp_file="/tmp/subshell-$$.txt"
df | grep "^/" > $tmp_file
while read name total used free other
do
    let sum+=free
done < $tmp_file
rm -rf $tmp_file
echo $sum


###--------------脚本5------------------###
# 加载其他外部命令或加载其他变量的脚本也会开启子shell
#!/bin/bash
file="/etc/passwd"
password="I-have-a-dream"
error_info="Please try again later"
# 另起一个脚本
#!/bin/bash
pstree
bash /root/shell.sh/env.sh
echo "passwd=$password"    # 当执行的是bash后,子进程进入后就会退出所以变量不会有
echo "Error:$error_info"
​
source ./env.sh            # 当执行的是source后,不会进入子进程所以变量会一直存在
echo "passwd=$password"
echo "Error:$error_info"
​
###--------------脚本6------------------###
#!/bin/bash
# 使用&在后台开启子shell
count=0
for i in {1..254}
do
    ping -c1 -i0.2 -W1 172.17.0.$i && let count++ &
done
echo $coun

3.子shell与父shell进阶

1)fork方式

使用绝对路径或相对路径执行一个命令时,都会由父进程开启一个子进程

#!/bin/bash
sleep 5
/root/shell.sh/env.sh
cd /root/shell.sh/; ./env.sh
# 调用外部命令时会fork子进程,env.sh脚本就只有一个pstree

2)exec方式

使用exec不会开启子进程,而是使用新的程序替换当前shell环境,一般使用先把exec写入另外一个脚本,使用fork方式调用exec脚本

#!/bin/bash
# 使用exec方式调用外部命令或脚本
exec ls
echo "test"   # 此时不会执行,因为现在整个脚本只会执行ls
cd /etc       # 不会执行整个脚本只会执行ls
# 注意:当exec后面的参数是文件重定向时,不会替换当前shell,脚本后面也就不会有影响

3)source方式

可以不开启子shell,而是当前shell环境中将需要执行的命令加载进来,执行完加载命令之后,继续执行脚本后续指令

#!/bin/bash
# 使用source加载外部脚本
source /root/shell.sh/env.sh
echo "hi $password"
ls /
# source将env.sh脚本的内容加载到当前shell而,后面的直接调用

4.基础函数

###--------------脚本1------------------###
#!/bin/bash
# 定义函数.............................
mymkdir () {
mkdir /root/shell.sh/test
touch /root/shell.sh/test/hi.txt
}
​
mymkdir              # 调用函数
unset mymkdir        # 取消函数定义
​

###--------------脚本2------------------###
# 调用函数
#!/bin/bash
function print_usage() {
       cat << EOF
    Usage: --help | -h
      Print help information for script
    Usage: --memory | -m
      Monitor memory information
    Usage: --network | -n
      Monitor network interface information
EOF
}
case $1 in
--memory|-m)
        free;;
--network|-n)
        ip -s link;;
--help|-h)
        print_usage;;
*)
        print_usage
esac
​
###--------------脚本3------------------###
# 定义函数位置变量
mymkdir() {
mkdir -p $1
touch $1/$2;
}
mymkdir $1 $2   # 调用

5.函数进阶之作用域域返回值

###--------------脚本1------------------###
# 默认函数启动都是不开启子shell的,所以当外函数外定义变量函数内也可以用甚至可以修改,在函数内定义变量亦然;注:次变量与export声明的变量还是有区别的,只在当前shell有效
#!/bin/bash
# 默认定义变量为当前Shell中全局有效
global_var1="hello"
global_var2="world"
​
# 定义demo函数,在函数内定义新的变量,并修改函数。 
function demo() {
     echo -e "\033[46mfunction [demo] started....\033[0m"
     func_var="Topic"
     global_var2="this is xiugai"
     echo "$global_var1 $global_var2"
     echo -e "\033[46mfunction [demo] end.\033[0m"
}
demo
echo
echo -e "func_var=$func_var \nglobal_var1=$global_var1 \nglobal_var2=$global_var2"
# 此时发现变量global_var2修改了

​
###--------------脚本2------------------###
# 在函数内加入local就会声明局部变量,就不会修改外部的变量了
#!/bin/bash
global_var1="hello"
global_var2="world"
function demo() {
     echo -e "\033[46mfunction [demo] started....\033[0m"
     local global_var2="this is xiugai" # 这里声明局部变量local
     echo "$global_var1 $global_var2"
     echo -e "\033[46mfunction [demo] end.\033[0m"
}
demo
echo
echo "$global_var1 $global_var2"
## 注意函数不调用函数内定义的函数就不会生效
​
​
###--------------脚本3------------------###
# 注:如果在函数内写exit就会退出整个脚本,但是用return设置返回值就不会退出脚本,
#!/bin/bash
function demo1() {
      uname -r
}
​
demo2() {
     echo "start demo2"
     return 100
     echo "demo2 end"
}
​
demo3() {
     echo "hello"
     exit         # 注意这里使用了exit所以最后一个echo没有执行
}
​
demo1
echo "demo1 status $?"
demo2
echo "demo2 status $?"
demo3
echo "demo3 status $?"
​

6.进程控制与文件描述和管道

1)文件描述符

#!/bin/bash
ps aux                  # 查看并发进程
ls -l /proc/$$/fd       # 查看当前shell的文件描述符
ls -l /proc/1/fd        # 查看systemd的文件描述符
ps -ao user,pid,comm| grep vim  # 查看所有并发进程并过滤出vim进程的user,pid,comm字段
ls -l /proc/23078/fd | tail -1  # vim默认会将所有文件存入该*.swp文件中,只有保存才会写入
​
exec 12> test.txt               # 创建仅可输出的文件
echo hello >&12                 # 通过&12调用文件描述符
exec 12<&-                      # 关闭输出描述符
exec 13< test.txt               # 创建仅可输入的文件,文件尽可读取一次
exec 13<&-                      # 关闭输入描述符
exec 13<>test.txt               # 创建可读写的文件描述
cat &<14                        # 读取文件内容
echo "Rick" >&14                # 重定向写入
exec 14<&-                      # 关闭输入描述符
read -u12 content               # 读取一行赋值给变量(echo $cotent查看)【注:每次仅可以读
取一行,读取结束为空】
read -u12 -n1 content           # 仅读取一个字符的数据-n指定个数

2)密名管道符

#!/bin/bash
mkfifo pipe_file1                  # 创建命名管道,不指定权限
mkfifo -m 644 pipe_file2           # 创建命名管道,指定权限
ls -l pipe_file1                   # 查看文件属性,第一列为pecho "hello world" > pipe_file1    # 使用该命令会进入(交互)阻塞状态,只有去查看才会结束
cat pipe_file1                     # 另开个终端,上面的就会结束,如果上面的没有写入数据>会进入(交互)阻塞状态,写入才会结束

###————————————————————————————————————————————————————————————###
#!/bin/bash
# 创建命名管道文件,并绑定固定的文件描述符
pipefile=/tmp/procs_$$.tmp
mkfifo $pipefile
exec 12<>$pipefile
​
# 通过文件描述符往管道中写入5行任意数据,用与控制进程数
for i in {1..5}
do
     echo "" >&12 &
done
# 通过read -u命令选项指定从特定的文件描述符中读取数据行
for j in {1..20}
do
    read -u12
    {
        echo -e "\033[32mstart sleep No.$j\033[0m"
        sleep 5
        echo -e "\033[31mstart sleep No.$j\033[0m"
        echo "" >&12
    } &
done
wait
rm -rf $pipefile

7.排序算法

1)冒泡排序

###---------脚本1随机6个数字,对比5次-------###
#!/bin/bash
for i in {1..6}
do
    read -p "请输入数字:" tmp
    if echo $tmp | grep -qP "\D" ;then
         echo "您输入的不是数字"
         exit
    fi
    num[$i]=$tmp
done
echo "您输入的数字序列为:${num[@]}"
​
# 冒牌排序
# 使用i控制进行几轮比较,使用j控制每轮比较的次数,
# 对6个数字而言,需要进行5论比较,每进行一轮后,下一轮就可以少比较一次
for ((i=1;i<=5;i++))
do
    for ((j=1;j<=$[6-i];j++))
    do
        if [ ${num[j]} -gt ${num[j+1]} ];then
            tmp=${num[j]}
            num[$j]=${num[j+1]}
            num[j+1]=$tmp
        fi
    done
done
echo "排序后的数字为:${num[@]}"
​
​
###---------脚本2-------###
#!/bin/bash
# 根据进程所占物理内存大小进行排序
​
# 保存系统所有进程的名称及物理内存大小的所有数据文件
tmpfile="/tmp/procs_mem_$$.txt"
ps --no-headers -eo comm,rss > $tmpfile
​
# 定义冒泡排序
# 使用i控制几轮的比较,使用j控制比较的次数
# 使用变量len读取数组个数,根据内存大小排序,并调整对应的进程名称顺序
bubble() {
local i j
local len=$1
for  ((i=1;i<=$[len-1];i++))
do
    for  ((j=1;j<=$[len-1];j++))
    do
        if [ ${mem[j]} -gt ${mem[j+1]} ];then
            tmp=${mem[j]}
            mem[$j]=${mem[j+1]}
            mem[j+1]=$tmp
            tmp=${name[j]}
            name[$j]=${name[j+1]}
            name[j+1]$tmp
        fi
    done
done
echo "排序后进程序列"
echo "----------------------------------------------------------"
echo "${name[@]}"
echo "----------------------------------------------------------"
echo "${mem[@]}"
echo "----------------------------------------------------------"
}
​
i=1
while read proc_name proc_mem
do
    name[$i]=$proc_name
    mem[$i]=$proc_mem
    let i++
done < $tmpfile
rm -rf $tmpfile
bubble ${#mem[@]}

2)快速排序

#!/bin/bash
num=(5 3 8 4 7 9 2)
quick_sort() {
    # 先判断需要进行比较的数字个数,$1是数字最左边的坐标,$2是数组的最右边的坐标
    # 当左边的坐标小于右边的左标,表示需要排序的数字只有一个,不需要排序直接退出函数
    if [ $1 -ge $2 ];then
        return
    fi
    # 定义局部变量,base为基准数字,这里选择的是最左边的数字num[$1]
    # i表示左边的坐标,right表示右边的坐标(也可以用i和j表示)
    local base=${num[$1]}
    local left=$1
    local right=$2
    # 在排序的数字序列中,比基数大的数字放右边,比基准数小的数字放左边
    while [ $left -lt $right ]
    do
         # right向左移动,查找比base基数小的元素
         while [[ ${num[right]} -ge $base && $left -lt $right ]]
         do
         let right--
         done
         # left向右移动,查找比基数base大的元素
         while [[ ${num[left]} -le $base && $left -lt $right ]]
         do
             let left++
         done
         if [ $left -lt $right ];then
             local tmp=${num[$left]} 
             num[$left]=${num[right]}
             num[$right]=$tmp
         fi
    done
# 将基数字与left坐标元素交换
num[$1]=${num[left]}
num[left]=$base
​
# 递归调用快速排序算法,对i左边的元素快速排序
quick_sort $1 $[left-1]
# 递归调用快速排序算法,对i右边的元素快速排序
quick_sort $[left+1] $2    
}
​
# 调用函数对数组进行排序,排序后输出数组的所有元素
quick_sort 0 ${#num[@]}
echo ${num[*]}

3)插入排序

#!/bin/bash
set -uex
# 随机创建5个数字赋值给数组变量num
for x in {1..5}
do
    read -p "请输入整数:" tmp
    num[$x]=$tmp
done
# 默认第一个已经为有序数字
# 使用变量j对比
for ((i=2;i<=5;i++))
do
# 使用j控制第i个元素前面需要比较数字
# j从第i-1个数字元素开始,每循环一次往前移动一位
    tmp=${num[i]}
    for ((j=$[i-1];j>=0 && $tmp

4)计数排序

#!/bin/bash
# 创建需要排序的数组
num=(2 8 3 7 1 4 3 2 4 7 4 2 5 1 8 5 2 1 9)
# 创建另一个对应上面最大值得数组的初始值为0,最大值为9所以创建10个
count=(0 0 0 0 0 0 0 0 0 0)
# num数组中有19个数,小标为0~18,使用循环读取num每个元素的值
# 以每个元素的值为count的下标,进行自加1的统计运算
for i in `seq 0 18`
do
    let count[${num[$i]}]++
done
# 使用循环读取count数组中的每个元素值(也就是次数)
# 根据次数打印对应下标
for i in `seq 0 9`
do
    for j in `seq ${count[i]}`
    do
         echo -n "$i"
    done
done
echo
​
###---------脚本2---------------###
# 自动分析排序数组最大值
#!/bin/bash
# 创建需要排序的数组
num=(2 8 3 7 1 4 3 2 4 7 4 2 5 1 8 5 2 1 9)
# 自动分析排序数组最大值
max=${num[0]}
for i in `seq $[${#num[@]}-1]`
do
    [ ${num[i]} -gt $max ] && max=${num[i]} 
done
for i in `seq 0 $max`
do
     count[$i]=0
done
​
for i in `seq 0 $[${#num[@]}-1]`
do
    let count[${num[$i]}]++
done
# 使用循环读取count数组中的每个元素值(也就是次数)
# 根据次数打印对应下标
for i in `seq 0 $[${#count[@]-1}]`
do
    for j in `seq ${count[i]}`
    do
         echo -n "$i"
    done
done
echo

8.花阔号的使用

#!/bin/bash
echo {a,b,c}    # 对字符串扩展
echo {hello,world} 
echo {22,33,44,55} 
echo {a..z}     # 对字符串序列扩展
echo {a..z..2}  # a至z,步长为2
echo {a..z..3}
echo {1..9..2}  # 1至9,步长为2
# 注花括号使用不可以使用引号
echo t{i,o}p    # 花括号前后可以添加字符串并支持扩展嵌套
echo t{o,e{a,m}}p
mkdir -p /test/t{o,e{a,m}}p
touch /test/t{o,e{a,m}}p/{a,b,c,d,e}t.txt   # 可以用tree /test查看
cp /test/top/at.txt{,.bak}   # 利用扩展备份文件使用ls /test/top/at*查看
mv /test/top/at.txt{,bt.doc} # 利用扩展,重命名

9.波浪号

#!/bin/bash
echo ~ # 显示当前用户的家目录
echo ~/test
echo ~tom  # 显示特定用户的家目录
echo ~+    # 显示当前工作目录
echo ~-    # 显示当前工作目录的前一个目录

10.变量替换

#!/bin/bash
# 正常的调用
h="go Spurs Go"
echo $h
echo ${h} # 防止与其他字符混淆
​
# 间接的调用变量,类似与调用数组的变量
player="DUNCAN"
mvp=player
echo ${mvp}      # 直接返回变量player本身
echo ${!mvp}     # 间接的调用player变量的值,不过尽可以实现一层的
​
# 定义初值(当变量非空时,返回变量本身)
echo $animals   # 当调用的变量为空或不存在是不会打印东西的
echo ${animals:-dog} # 此时变量还是没有赋值,所以会打印出dog
echo ${animals:=dog} # 此时变量没有赋值,会打印出dog并给变量赋值
​
# 判断变量是否有值
echo ${ai:?'你没有输入的变量的值'} 
echo ${key:+lock}    # 当变量有值时返回值,没有值则返回空
​
###---------脚本2---------------###
#!/bin/bash
home="Hello World I'am have python book"
echo ${home:2}    # 从位置2开始截取到变量的末尾,没有设置截取范围默认截取到末尾
echo ${home:14}   # 从位置14开始截取到变量的末尾
echo ${home:14:6} # 从位置14开始截取6个字符结束
echo ${home#Hello}    # 从左往右匹配将匹配的Hello删除,掐头,只匹配第一个
echo ${home#World}   # 变量开头无法匹配World,返回变量本身
echo ${home#*o}   # 匹配o及左边的所有内容删除,但只匹配第一个
echo ${home##*o}  # 匹配o及左边的所有内容删除,匹配所有
echo ${home%book} # 从右往左匹配将匹配的book删除,去尾,只匹配第一个
echo ${home%%o*} # 从右往左匹配将匹配的所有o删除,,匹配所有

11.高级变量替换

#!/bin/bash
tools=(this is a tools)
echo ${tools[0]}      # 正常提取数组中的一个元素
echo ${tools[0]:0:2}  # 对数组中的某一个元素截取字符串,从第0个开始往后截取两个
echo ${tools[1]:0:2}  
echo ${tools[2]:3:2}
echo ${tools[4]#*o}   # 根据数组中某个元素进行掐头操作
echo ${tools[4]##*o}
echo ${tools[0]%*s}   # 根据数组中某个元素进行去尾操作
echo ${tools[0]%%*s}
​
###---------脚本2---------------###
#!/bin/bash
echo ${!U*}     # 列出以U开头的所有变量,打印出来会成为一个整体
echo ${!U@}     # 列出以U开头的所有变量,打印出来会成为多个
echo ${!HO@}
​
test1=(11 22 33 44)
test2=(77 88 99 00)
echo ${!test1[*]}   # 返回数组下标
declare -A str      # 定义关联数组
str[a]=aaa
str[b]=bbb
str[word]="key value"
echo ${!str[*]}     # 打印所有数组的下标
echo ${!str[@]}
​
play="Go Spurs Go"
echo ${#play}       # 统计变量长度
​
hi=(hello the world)
echo ${#hi}         # 没有写数组下标默认hi[0]的长度
echo ${#hi[0]}
echo ${#hi[1]}
​
phone=18811011011
echo ${phone/1/x}     # 将1替换成x,仅替换第一个
echo ${phone//1/x}    # 将1替换成x,替换所有
echo ${phone/110/x}   # 仅替换第一个110为x
echo ${phone//110/x}  #替换所有110为x
echo $phone           #注意变量不会真的改变
​
lowers="hello the world"
echo ${lowers^}       # 将首字母替换为大写
echo ${lowers^^}      # 将所有字母替换为大写
echo $lowers          #注意变量不会真的改变
echo ${lowers^h}       # 将第一个h字母替换为大写
echo ${lowers^^h}      # 将所有h字母替换为大写
echo ${lowers^^[heo]}      # 将所有h,e,o字母替换为大写
​
uppers="HELLO THE WORLD"
echo ${lowers,}       # 将首字母替换为小写
echo ${lowers,,}      # 将所有字母替换为小写
echo "${lowers,H}"    # 将第一个H字母替换为小写
echo "${lowers,,H}"   # 将所有H字母替换为小写
echo ${lowers,,[HEO]} # 将所有H,E,O字母替换为小写
​
​
​
###---------脚本3---------------###
#!/bin/bash
# 批量修改扩展名
if [[ -z "$1" || -z "$2" ]];then
echo "Usage:$0 旧扩展名 新扩展名"
exit
fi
for i in `ls *.$1`
do
    mv $i ${i%.$1}.$2
done
​
###---------脚本4---------------###
#!/bin/bash
# 批量修改扩展名,指定路径
if [[ -z "$1" || -z "$2" || -z "$3" ]];then
echo "Usage:$0 指定路径 旧扩展名 新扩展名 "
exit
fi
for i in `ls $1/*.$2`
do
    mv $i ${i%.$2}.$3
done

12.命令替换

#!/bin/bash
echo -e "system CPU load:\n$(date +%Y-%m-%d;uptime)"  # 嵌套替换
echo -e "system CPU load:\n`date +%Y-%m-%d;uptime`"
echo "系统当前登录人数:$(who|wc -l)"
echo "系统当前登录人数:`who|wc -l`"
du -sh $(pwd)

13.算术替换

#!/bin/bash
i=1                # 定义初始值
echo $((i++))      # 先显示i的值,再自加1
echo $((i++))      # 再加一
echo $((++i))      # 先自加1,再显示i的值
echo $((--i))      # 先自减1,再显示i的值
echo $((1+2))      # 加法运算
echo $((2*3))      # 乘法运算
echo $((20/5))     # 除法计算
echo $((20%5))     # 取余计算,20除5余0
echo $((2**3))     # 幂运算
echo $((2>=3))     # 逻辑比较,1表示对,0表示错
echo $((8>=3))
echo $((3>=3))
echo $((3>3))
echo $((3<=3))
echo $((3==3))
echo $((3==4))
echo $((3!=4))

14.进程替换

#!/bin/bash
who | wc -l    # 通过管道传递给另一个进程作为输入的内容
wc -l <(who)   # 使用<(who)会把结果保存到/dev/fd/63这个文件描述符,然后wc -l输出,文件描
述符是实时动态的当进程结束,文件描述符失效
paste <(cut -d: -f1,6 /etc/passwd) <(cut -d: -f2 /etc/shadow)  # paste整合文件/etc/passwd第1列与第6列和文件/etc/shadow第2列
ls /etc/*.conf > /tmp/conf.log    # 重定向后屏幕无输出
ls /etc/*.conf | tee /tmp/conf.log # 覆盖重定向后屏幕有输出
ls | tee >(grep sh$ >sh.log) >(grep conf$ > conf.log) # 使用ls|tee的输出结果通过进程替换
写入临时的文件描述符,最后通过grep对文件描述符进行过滤见以sh结尾的文件名输出到sh.log,以
conf结尾的文件重定向到conf.log

15.单词切割

#!/bin/bash
read -p "请输入3个字符:" x y z # 默认使用空格TAB制表符和换行符做分词处;请输入3个字符 1 2 3 
read -p "请输入3个字符:" x y z # 请输入3个字符: 123 ;把123作为一个整体赋值给x
IFS=$','                        # 设置分词符号
read -p "请输入3个字符:" x y z # 请输入3个字符: 1,2,3 ;这样就会把123分别给xyz赋值

16.路径替换

#!/bin/bash
touch {a,A,b,B}.txt
shopt -s nocaseglob     # 设置shell属性不区分大小写
shopt nocaseglob        # 查看设置好的属性,检查结果为on
ls b*                   
shopt -u nocaseglob     # 关闭忽略大小写属性
ls !(a.txt)             # 列出除a.txt以外的所有文件名
ls !(a.txt|b.txt)
rm -rf [a-z].txt        # 使用通配符删除文件
basename /a/b/c/d.txt   # 获取一个路径中的文件名
dirname /a/b/c/d.txt    # 仅保留路径,删除文件名
​
###---------脚本2---------------###
​
#!/bin/bash
# 循环多个文件进行备份操作
for i in `ls /etc/*.conf`
do
     tar -czf /root/log/$(basename $i).tar.gz $i
done
​

17.随机获取密码

###---------使用字符串随机获取------------###
#!/bin/bash
# 使用字符串截取的方式生成随机密码
# 定义变量:10个数字+52个字符
key="abcdefghijkmnopqstuvwxyzABCDFGHIJKMNOPQSTUVWXYZ0123456789"
randpass() {
     if [ -z "$1" ];then
          echo "randpass 函数需要一个参数,用来指定提取的随机个数"
          return 127
     fi
# 调用$1参数,循环提取任意一个数据字符
# 用随机数对62取余,返回的结果为[0-6]
    pass=""
    for i in `seq $1`
    do
          num=$[RANDOM%${#key}]
          local tmp=${key:num:1}
          pass=${pass}${tmp}
    done
    echo $pass
}
​
# 创建临时测试账户,为账户配置随机密码,并将密码保存至/tmp/pass.log
useradd tomcat
passwd=$(randpass 6)
echo $passwd | passwd --stdin tomcat
echo $passwd > /tmp/pass.log
​
###---------使用命令随机获取------------###
#!/bin/bash
uuidgen     # 生成随机字符串
openssl rand -hex 1    # 生成16进制随机字符串
openssl rand -hex 2
openssl rand -base64 1  # 生成含特殊符号的随机字符串,使用base64算法生成随机数据,其最终
长度为(n/3)向上取整后乘以4
openssl rand -base64 6
date +%s                # 通过时间提取随机数字
​
###---------使用块设备随机获取------------###
#!/bin/bash
cat /dev/random  # 查看产生随机数据的设备文件
cat /dev/urandom # 与上一样
strings /dev/random
tr -cd '_a-zA-Z0-9' /dev/random  | head -c 10 # 查看产生随机数据的设备文件,只提取10位包
含字符,数字和下滑线的数据
​
​
###---------使用Hash值随机获取------------###
#!/bin/bash
echo a | md5sum          # 使用md5的hash值
echo a | md5sum | cut -d' ' -f1
md5sum /etc/passwd | cut -d' ' -f1
​
​
###---------使用进程数获取------------###
#!/bin/bash
# 根据进程号生成随机文件
touch /tmp/$$.tmp
​
# 根据进程数量生成随机文件
pnum=`ps aux | wc -l`
touch /tmp/$pnum.tmp
​
# 根据文件个数生成随机文件
fnum=`find /etc | wc -l`
touch /tmp/$fnum.tmp
​
# 根据文件行数生成随机文件
cnum=`cat /var/log/messages | wc -l`
touch /tmp/$cnum.tmp

18.shell解释器和属性

#!/bin/bash
set -o      # 查看shell属性
shopt       # 与上类似
# 在字shell执行父变量
test=123
set -a      # 开启allexport
bash
echo $test  # 获取父shell
set +a      # 关闭allexport
​
# Bash默认支持花括号,braceexpand
echo {a..c} # 默认支持花括号
set +B      # 关闭花括号扩展
set -B      # 开启花括号扩展
​
# 当命令返回值非0时脚本直接退出
set -e 
useradd root
echo "123456" | passwd --stdin root
echo "已经将root密码修改改为:123456"
​
# 使用Hash记录命令
hash         # 通过hash命令查看记录列表
hash -d ip   # 删除Hash表中的记录,注意当命令位置移动,在使用会报错,这个使用只要删除记录
就可以了
hash -r      # 清空整个Hash表
set +h       # 禁用Hash表
set -h       # 开启Hash表
​
# 使用histexpand支持使用!调用历史命令
!yum         # 默认支持
set +H       # 禁用histexpand
​
# 设置noclobber防止属性被覆盖
echo hello > test.txt  # 默认支持覆盖重定向
set -C       # 设置noclobber防止被覆盖
​
# 开启bash的nounset属性,防止变量未定义
set -u
useradd $1
echo "$2" | passwd --stdin $1
​
###-------------使用锁文件执行脚本--------###
#!/bin/bash
# 通过设置锁文件防止脚本重复执行
trap  'rm -rf /tmp/lockfile;exit' HUP INT
# 检查是否锁文件
lock_check() {
   if (set -C; :> /tmp/lockfile) 2>/dev/null ;then
       backup
   else
      echo -e "\003[91m Warning: 其他用户在执行该脚本.\033[0m"
   fi
}
​
# 备份前锁文件,sleep10防止小数据备份太快,无法验证重复执行
backup() {
   touch /tmp/lockfile
   mysqldump --all-database > /var/log/mysql-$(date +%Y%m%d).bak
   sleep 10
   rm -rf /tmp/lockfile
}
lock_check
backup

19.初始化命令shopt终端与tput终端

####-----------shopt终端----------------####
#!/bin/bash
hello=/etc              # 定义变量
shopt -s cdable_vars    # 开启cdable_vars属性
cd hello                # 没有heelo目录但进入/etc
​
shopt -u cdable_vars    # 关闭cdable_vars属性
​
shopt -s cdspell        # 开启自动纠错属性
cd /ect                 # 只能纠正部分错误
​
# 如果hash表中无法找到命令会报错,开启checkhash属性,没有找到命令继续正常的命令路径搜索
ip link show
hash
mv /usr/sbin/ip /usr/bin
ip link show            # 此时就会报错
shopt -s checkhash      # 开启checkhash
ip link show
​
# cmdhist属性可以将多条命令保存到一条记录里,默认开启
for i in {1..3}
do
    echo $i
done
​
history | tail -2   # 历史记录一行
shopt -u cmdhist    # 关闭cmdhist,此时就会记录多条记录
shopt -s cmdhist    # 开启cmdhist
​
​
###---------------tput终端---------------###
#!/bin/bash
tput cols       # 显示当前终端的列数
tput lines      # 通过lines显示当前终端的行数
tput clear      # 通过clear终端清屏,与clear和ctrl+l一样
tput cup 10 20  # 移动光标至10行20列
tput sc         # 保存当前光标
tput rc         # 恢复光标位置
tput civis      # 不显示光标
tput cnorm      # 显示光标
tput civvis
tput bold       # 终端设置闪烁模式
tput blink      # 终端设置加粗
tput rev        # 当前终端字体与背景替换
tput smcup      # 保存屏幕状态
tput rmcup      # 恢复屏幕状态
tput sgr0       # 取消所有终端属性
reset           # 重置终端为初始状态

20.trap信号捕捉

#!/bin/bash
kill -l             # 显示所有信号
ps -aux | grep -v grep | grep loop.sh  # 过滤指定进程
kill -19 14448      # 传递暂停信号
kill -CONT 14448    # 传递恢复信号
kill -SIGINT 14448  # 中断结束循环脚本
​
###---------------trap代码---------------###
#!/bin/bash
# 用trap代码实现捕捉信号
trap 'echo "打死不中断|睡眠.";sleep 3' INT TSTP
trap 'echo 测试;sleep 3' HUP
while :
do
    echo "signal"
    echo "demo"
done
# 注意shell不能捕捉所有代码,像TERM,KILL之类的

21.shell脚本内容排错与进度条

#!/bin/bash
bash -x a.sh # -x排错功能,(++)表示该命令在子shell执行或者在脚本中使用一个-x
​
###---------------脚本1---------------###
# 当执行一个脚本时,可以额外添加一些命令如下
#!bin/bash
i=1
while [ $i -le 100 ] 
do
     let sum+=i
done
echo $sum     # 当执行时会一直陷入死循环应为i始终为1
​
​
###---------------脚本2---------------###
#!/bin/bash
## 使用进度条作为提示符
# 防止Ctrl+c组合键无法结束进度条
trap 'kill $!' INT
# 定义函数;实现无限显示不换行的#符号
bar() {
    while :
    do
       echo -n '#'
       sleep 0.3
    done 
}
bar &
cp -r $1 $2
kill $!
echo "复制结束"
​
~]# chmod +x shell16.sh 
~]# ./shell16.sh /etc /mnt/
#############################复制结束
## 注:$!表示shell中的最后一个后台PID
​
###---------------脚本3-----------------###
#!/bin/bash
##使用背景色作为进度条
# 防止Ctrl+c组合键无法结束进度条
trap 'kill $!' INT
# 定义函数;实现无限显示不换行的#符号
bar() {
    while :
    do
       echo -ne '\033[42m \033[0m'
       sleep 0.3
    done 
}
bar &
cp -r $1 $2
kill $!
echo "复制结束"
​
###---------------脚本4-----------------###
#!/bin/bash
## 使用背景色作为进度条,复制大文件的进度条
# 防止Ctrl+c组合键无法结束进度条
trap 'kill $!' INT
# 定义函数,当宽度为50的范围内输出进度条,#和占用48个空间,竖线占用2个宽度#1个#组合47个>格子=48,2个#组合46个格子=48,3个#组合45个格子=48,依此类推
# 输出\r光标移至行首
bar() {
    while :
    do
        pound=""
        for ((i=47;i>=1;i=--))
        do
           pound+=#
           printf "|%s%${i}s|\r" "$pound"
           sleep 0.2
        done
    done
}
bar &
cp -r $1 $2
kill $!
echo "复制结束"
​
###---------------脚本5-----------------###
#!/bin/bash
## 使用指针
# 防止Ctrl+c组合键无法结束进度条
trap 'kill $!' INT
# 定义变量用来存指针
rotate='|/-\'
​
# 定义函数;实现指针进度条
bar() {
    printf ' '
    while :
    do
        printf "\b%.1s" "$rotate"
        rotate=${rotate#?}${rotate%???}
        sleep 0.2
    done
}
bar &
cp -r $1 $2
kill $!
echo "复制结束"
​
###---------------脚本6-----------------###
#!/bin/bash
## 百分比的样式
trap 'kill $!' INT
​
# 存储与目标大小,目标大小始终为0
src=$(du -s $1 | cut -f1)
dst=0
​
# 定义函数;实时对比源文件与目标文件的大小,计算复制进度
bar() {
    while :
    do
       size=$(echo "scale=2;$dst/$src*100" | bc)
       echo -en "\r|$size%|"
       [ -f $2 ] && dst=$(du -s $2 | cut -f1)
       [ -d $2 ] && dst=$(du -s $2/$1 | cut -f1)
       sleep 0.3
    done
}
bar $1 $2&
cp -r $1 $2
kill $!
echo "复制结束"
## 注意只支持一级目录

22.xargs参数传参

#!/bin/bash
cat /etc/redhat-release | xargs   # 将数据传递给其他程序,不指定程序时xargs默认传递给echo
cat /etc/redhat-release | xargs echo
cut -d: -f1 /etc/passwd | xargs
find /etc -name *.conf -type f | xargs
find /etc -name *.conf -type f | xargs grep "python" # 过滤查找文件
find /etc -name *.conf -type f | xargs ls -l         # 查找文件属性
echo /mnt/nginx/*  | xargs rm -rf                    # 删除文件或目录
cat /var/run/crond.pid | xargs kill                  # 成功杀死crond进程
find ./ -name "*.txt" -print0 | xargs -0 rm          # 指定结束符为NULL
xargs -a /etc/redhat-release echo                    # 读取参数传递给其他程序
seq 5 | xargs                                        # 默认一次调用全部参数
seq 5 | xargs -n 2                                   # 设置一次调用2个参数
echo "abcdefghijklnuvwxyz" | xargs -da               # 指定分隔符,默认tab空格,换行符
ls * .txt | xargs -I{} cp {} /tmp/                   # 复制所有txt文件到/tmp,大i指定替换
字符
ls * .txt | xargs -I[] cp [] /tmp/                   # 设置[]为替换字符串

23.shift移动参数

#!/bin/bash
# shift命令,左移位置参数
echo "arg1=$1,arg2=$2,ag3=$3,ag4=$4,ag5=$5,ag6=$6,count=$#"   # 正常显示
shift           # 左移一位,shift没有参数默认移动一位
echo "arg1=$1,arg2=$2,ag3=$3,ag4=$4,ag5=$5,ag6=$6,count=$#"   # 向左移一位
shift 2         # 在移动第一位之后再左移二位
echo "arg1=$1,arg2=$2,ag3=$3,ag4=$4,ag5=$5,ag6=$6,count=$#"   # 向左移两位
​
~]# bash shift.sh a b c 1 2 3
​
​
###---------使用shift判断文件是否存在------###
#!/bin/bash
## 测试文件为空则删除文件
if [ $# -eq 0 ];then
    echo "用法:$0 文件名...."
    exit 1
fi
# 测试位置变量个数,个数为0时循环结束
while (($#))
do
    if [ ! -s $1 ];then
         echo -e "\033[31m$1为空文件,正在删除该文.\033[0m"
         rm -rf $1
    else
         [ -f $1 ] && echo -e "\033[32m$1为非空文件.\033[0m"
         [ -d $1 ] && echo -e "\033[32m$1为目录,不是文件名.\033[0m"
    fi
    shift
done
​
​
###-------------对文件进行切割------------###
#!/bin/bash
dd if=/dev/urandom of=/root/test.txt bs=1M count=500      # 生成随机500MB的文件
split -b 20M test.txt test_                               # 把文件分20M切割
split -l 1000 test.txt test_                              # 按行数切割
split -b 4 test.txt multi_                                # 按四个字节切割,但不会换行

24.其他

df / | tail -n +2     # 去掉标题从第二行打印
​
# tr的使用
#!/bin/bash
echo "hello then world" | tr 'h' 'H' # 小写h转换成大写
echo "hello then world" | tr 'a-z' 'A-Z' # 小写h转换成大写
echo "HELLO THEN WORLD" | tr 'A-Z' 'a-z' # 大写h转换成小写
echo "hello then world" | tr h 9         # 将h替换为9
echo "hello then world" | tr h x         # 将h替换为x
tr 'a-z' 'A-Z'  < /etc/passwd            # 从标准输入读取数据
echo "aaaa" | tr -s a                    # 删除重复的a
echo "aaabbb" | tr -s ab                 # 删除重复的a和b,非连续的字符不会删除
echo "hello then world" | tr -d 'h'      # 删除字母h
echo "hello then world" | tr -d 'ho'     # 删除字母h和o
echo "hello then world" | tr -d 'a-e'    # 删除包含a-e的所有字母
echo "hello then world" | tr -cd 'o'     # 对字母o取反,也就是只取o
echo "hello 99987" | tr -cd a-z          # 仅保留小写字母
echo "hello 666"   | tr -cd 0-9          # 仅保留数字
echo "hello 666"   | tr -cd '0-9\n'      # 仅保留数字和换行符
echo "hello 666"   | tr -c '0-9' x       # 将数字外的符号替换为x
echo "hello789"    | tr -c 'a-z' x       # 将小写外的符号替换为x
​
​
# 换行的使用
#!/bin/bash
echo -e "abc\r12"           # 输出abc后光标移至行首,字符ab被替换成12,c没有影响
echo -e "abcdef   \r12    " # 输出abcdef后光标移至行首,字符ab被替换成12后面四个空格替换
cdef,所以输出12
i=5
printf "|%s%${i}s|" abc     # |abc    |abc后面有5个空格
​
​
# printf打印指定字符
#!/bin/bash
printf "%10s" xyz          # 指定宽度10,宽度不够自动补空格
printf "%3s" xyzabc        # 指定宽度3,实际宽度大于3
printf "%.2s" xyzabc       # 指定实际字符中的两个字符
printf x;printf "\b%s" y   # 显示字符x,删除x后再显示字符y
printf x;printf "\b%.1s" yz   # 显示字符x,删除x后仅显示字符y
​
# 使用替换改变位置
#!/bin/bash
rotate='|/-\'
echo ${rotate#?}     # 对变量掐头,删除第一个字符
echo ${rotate%???}   # 对变量去尾
rotate2=${rotate#?}${rotate%???} # 将第一个字符移动到最后
echo ${rotate2}
​
​
​
## 每次复制1mb,生成500次,生成一个500mb的数据
dd if=/dev/urandom of=/root/test.txt bs=1M count=500

你可能感兴趣的:(SHELL,bash,linux,开发语言,shell,运维)