文章目录
Shell脚本
预备知识
在ubuntu下,/usr/bin/bash存放shell的bash脚本解释器,/usr/bin/bash本质上就是sh的长连接。
使用echo $SHELL可以查看存储脚本的路径的环境变量。
设置脚本文件 :vim ./mybash.sh先设置一个脚本文件,脚本文件第一行要使用!#/bin/bash表示shell的bash脚本解释器的位置,使用#/bin/sh也一样,目的是为了寻找到脚本解释器的位置。所谓的脚本,就是把多个shell命令,放到一个文件里一起执行。python也有对应的脚本解释器python.exe
执行脚本 :如果当前脚本文件有可执行权限,可以直接.mybash.sh执行。第二种方法:使用bash ./mybash.sh执行
变量
在开机后,系统会先从/etc/profile读取全局的系统环境变量信息,然后从~/.bashrc读取用户的系统变量信息,除了环境变量外,还包括命令行解释器、颜色、bash这是从这两个文件下读取配置信息完成初始化的,对于vim,在/etc/.vimrc文件下完成配置,使用set ts=4可以配置Tab键表示4个空格。修改后记得使用source + 指定配置文件完成重新加载。
变量作用域
export
- Linux 环境变量章节也有讲解,作为内建命令,完成对父进程的环境变量的添加。这里添加的是临时的系统变量,只能在当前会话下有作用,可以交给子进程使用------所以说,如果此时执行bash脚本,可以看到export添加的系统变量,因为
bash本来就是一个进程(使用ps命令可以查看bash的进程id,此外,查看时能看到查到的bash应该名字是--bash,这是因为--bash表示父bash,也就是你当前的命令所处的bash进程,你使用bash test.sh执行脚本时,会添加一个新的bash进程,这个被称为子bash)。 - 除了export可以设置系统变量外(注意了,只是临时的系统变量,只能在当前会话使用),可以通过修改
/etc/profile和~/.bashrc来永久添加系统变量,可以在所有会话内使用啦
i=10
如果只是以变量=值的格式设置变量,这个变量是临时的用户变量,只能在当前会话,且当前bash内有效,如果使用bash test.sh运行新的脚本,在新bash不能使用这个变量
readonly
使用readonly修饰变量表示只读,类似const,作用域参考上面两条。
特殊变量
$+数字
在学习cpp java时候我们都知道有一个东西叫做命令行参数,shell脚本也有这个东西,可以使用1表示传入的第一个参数,2表示第二个传入的参数
$#
表示传参个数,比如使用bash mybash.sh a b c d此时,echo $#打印4,echo $2打印b
$*
这个变量在for循环处使用样例来讲解
$@
这个变量在for循环处使用样例来讲解
运算符
A=3, B=8, 如果我们希望给D赋值A+B
D='expr $A+$B'。这里expr 表示计算,``可以将括住的内容作为一条完成的内容执行,不会截断,并且保留命令结果返回给DD=$[A+B]或者D=$[$A+$B]D=$((A+B))或者D=$(($A+$B))
综合运算
shell
expr `expr 3+5` \* 5 # \*表示转义
echo $[(3+5)*5] #等价
条件判断
大小比较
-lt less than
-le less equal
-eq equal
-ne no equal
-gt greater than
-ge greater equal
字符串比较
=
权限判断
-r -w -x
文件类型
-f 是否是普通文件
-e 是否存在
-d 是否文件存在且文件是目录
if语句
语法格式
shell
if [ 条件表达式 ];then #注意了,[右边有一个空格,]左边有一个空格
程序
fi
# 或者
if [ 条件判断式 ]
then
程序
fi
使用样例
shell
# 写一个判断人是否是少年、青年、老年的脚本,通过命令行传参
#!/bin/bash
if [ $1 -le 18 ];then
echo "年龄是$1,少年"
elif [ $1 -gt 18 -a $1 -lt 40 ];then # -a表示并且,-o表或者,可以使用[[]]配合&& ||使用,表示 bash 扩展语法,支持 &&/||
echo "年龄是$1,青年"
else
echo "年龄是$1,中老年"
fi
~
case
shell
case $1 in
"start")
echo "你写入start"
;; # 等价于其他语言的break
"end")
echo "你写入end"
;;
*) # 等价于其他语言的default
echo "其他"
;;
esac
for
shell
#!/bin/bash
# 写一个1+...+100
sum=0
for i in {1..100};do # 这个i in {1.100}可以改为((i=1;i<=100;i++))
sum=$[sum+i]
done
echo $sum
# 此外,for in的功能十分强大,像是遍历ls结果也可以做到,注意的是,如果文件有空格,就会有问题,因为本质就是通过空格和换行符分隔给不同i
for i in `ls /`;do
echo $i
done
# 可以通过修改 IFS 变量临时改变分隔符,例如:
# 仅用换行符作为分隔符(忽略空格)
IFS=$'\n' # 将IFS设为换行符,这里注意,\n和$'\n'不一样,
str="apple banana
cherry date"
for fruit in $str; do
echo "水果:$fruit"
done
unset IFS # 恢复默认IFS
讲一下前文提到的\* @
代码:
shell
#!/bin/bash
echo "=== 1. \$* 遍历(不加引号) ==="
for arg in $*; do
echo "参数:$arg"
done
echo -e "\n=== 2. \$@ 遍历(不加引号) ==="
for arg in $@; do
echo "参数:$arg"
done
echo -e "\n=== 3. \"\$*\" 遍历(加引号) ==="
for arg in "$*"; do
echo "参数:$arg"
done
echo -e "\n=== 4. \"\$@\" 遍历(加引号) ==="
for arg in "$@"; do
echo "参数:$arg"
done
# 执行脚本
# bash ./test.sh "hello world" "foo bar" baz
执行结果:
shell
=== 1. $* 遍历(不加引号) ===
参数:hello
参数:world
参数:foo
参数:bar
参数:baz
=== 2. $@ 遍历(不加引号) ===
参数:hello
参数:world
参数:foo
参数:bar
参数:baz
=== 3. "$*" 遍历(加引号) ===
参数:hello world foo bar baz # 所有参数合并成一个字符串
=== 4. "$@" 遍历(加引号) ===
参数:hello world
参数:foo bar
参数:baz # 每个参数保持原始格式
关键分析 "$*":
-
无论传入多少参数,
"$*"都会将它们拼接成一个字符串,拼接符是IFS的第一个字符(默认是空格)。例如上面的参数被拼接为"hello world foo bar baz"。 -
用
for arg in "$*"遍历时,循环只会执行一次 ,arg变量的值是整个合并后的字符串(而非逐个参数)。 -
仅当需要将所有参数作为一个整体处理时使用(例如作为单个字符串传递给其他命令),不适合遍历单个参数。
"$@":
- 保留每个参数的原始格式(含空格的参数仍作为整体,推荐使用)。
while
shell
i=1
sum=0
while [ $i -le 100 ];do
let sum=sum+i
let i++
done
补充let命令
let 是用于执行算术表达式的命令,可以直接进行整数运算(如加减乘除、自增自减等),并支持变量赋值。
支持基本算术运算与赋值,还有 多个表达式同时执行
shell
a=2
b=3
let e=a*b # *不用转义
let "c=a+b, d=a*b" # 同时计算 c 和 d
echo "c = $c, d = $d" # 输出:c = 5, d = 6
*支持+±-和+= -= =
函数
basename
basename /usr/bin/a.out .out打印a,其中basename去除前缀,直到最后一个/ 后面的.out表示去除suffix(后缀)
dirname
dirname /home/shell/test01.sh 打印/home/shell 返回是文件所在的目录的绝对路径
实战:
shell
#!/bin/bash
# 写一个把"./"目录下所有后缀是.txt文件改为.sh后缀
dir = "./"
for i in `ls ${dir}*.txt`;do
filename=`basename $i .txt`
new_name="${filename}.sh"
mv "$i" "$new_name"
done
自定义函数
注意的是,如果自定义函数想要return值,只能return 0~255的整数,结果通过echo $?获取
shell
#!/bin/bash
function sum(){
let s=$1+$2
return $s
}
sum 100 200 # 这里超过了255,会发现返回值不是300.超过这个范围会被取模256.得44
echo $?
# 如果希望把更大的结果带出去
function sum2(){
let s=$1+$2
echo $s
}
result=`sum2 100 200`
echo result
实战:
shell
#!/bin/bash
# 计算阶乘
if [ $# -ne 1 ];then
echo "请输入一个参数即可"
exit -1;
fi
# 开始主逻辑
function jiecheng(){
if [ $1 -le 1 ];then
echo 1 # 当n=1时,返回1,``是把标准输出的结果传出去
return 0
elif [ $1 -gt 1 ];then
let prev_n=$1-1
tmp=`jiecheng $prev_n`
let now=tmp*$1
echo $now
return 0
fi
}
jiecheng $1
工具
cut
shell
cut -d ":" -f1-5 passwd
cut -d ":" -f1,2 passwd
# -d表示分隔符,后面接分隔符。-f表示列数,将指定列打印出来,passwd表示文件,可以使用管道进行读取其他结果
# 使用样例
grep "/bin/bash$" passwd | cut -d: -f1 # -d后面可以不加""
read
shell
read -t 3 -p "请在三秒内输入一个大于1的数字: " num # -t 表示最多阻塞时间,-p表示显示提示信息
sed
对输入流(文件或管道数据)进行行级别的编辑(如替换、删除、插入、追加等),其特点是按行处理、默认不修改原文件(除非指定)。
shell
# sed [选项] '命令' 输入文件
# -a add
# -d delete
# -s find and 替换
sed '1a hello world' seg.txt # 在第一行后添加hello world
# 注意,sed只是修改文件缓冲区,不修改文件,加-i选项才会修改文件
sed '/wo/d' seg.txt # 第一个/可以完成查找wo的工作,第二个/分割检索字段和操作字段
sed 's/wo/ni/g' seg.txt # s表示查找并且替换,wo表示谁需要被替换,ni表示替换成ni,g表示宣布替换(如果不添加g,每行只被替换一个)
sed -e '4d' -e 's/wo/ni/g' seg.txt # -e表示在指令生成文本修改,也就是可以同时执行多个指令
awk
shell
# awk [选项参数] '模式{动作}' 输入文件
awk -F: '{print $1 "," $2}' passwd # -F表示切割,$1表示第一列,$2表示第二列
awk -F: '/^laoxiao/{print $1 "," $7}' passwd # ^ 是一个正则表达式元字符,表示 "行的开头"
awk -F: 'BEGIN{print "username, shell"}{print $1 "," $2}END{print "结束"}' passwd # begin表示在最开始先运行,end表示在最后运行
sort
shell
# 默认以字符字典形式排序
# -n 数值大小拍
# -r 相反
# -t 指定分隔符
# -k 列数