IT序号网

shell脚本知识解答

flyfish 2021年09月05日 程序员 151 0
本文章主要介绍了shell脚本,具有不错的的参考价值,希望对您有所帮助,如解说有误或未考虑完全的地方,请您留言指出,谢谢!

操作系统简史

OS时代

  • 1973 贝尔实验室Unix AT&T Unix
  • 1982 BSD Unix
  • 1991 SUN Solarls

PC时代

  • 1975 乔布斯Apple
  • 1980 比尔盖茨DOS

GUI时代

  • 1979 乔布斯Mac
  • 1990 比尔盖茨Windows
  • 1994 Liunx

移动OS时代

  • 2005 Google收购Android
  • 2005 乔布斯IOS

shell

  • 1977 sh
  • 1989 bash

bash变量类型

# 整型 
a=1   
# 字符串 
b="hello word"     
# 布尔 
c=true  
# 数组 
array=(a b c)  
# 函数 
foo() { echo hello world }  
 
= 左右两边不要有空格 

变量的使用

# 定义变量a 
a=1   
# 定义变量b 
b="hello wrod" 
 
# 输出变量a的值  echo相当于python的print() $引用变量 
echo $a   
输出:1 
 
# 输出变量b的值 
echo $b 
输出:hello wrod 
 
# 双引号可以加变量,单引号加变量话当成字符串 
c="b=$b" 
输出:b=hello wrod 
c='b=$b' 
输出:b=$b 
 
# shell里面输出没有定义过的变量也不会报错,只是显示空 
echo $bbb 
 
# 字符串拼接变量 
echo ${b} hello shell 
输出:hello wrod hello shell 

浮点数的计算

bash默认不支持浮点数的除法,如果要使用可以使用下面的方式

# 计算浮点数 
awk 'BEGIN{printf 1/3}' 
输出:0.333333 
 
# 格式化显示 
awk 'BEGIN{printf("%.2f\n", 1/3)}' 
输出:0.33 

预定义变量

echo $PWD  # 当前路径和小写pwd一样 
echo $USER # 当前用户 
echo $HOME # 当前用户的家目录 
echo $PATH  # 当前的环境变量 

which 查看当前项目安装的路径

# 查看python安装的路径 
which python  
# which只能找在环境变量存在的路径 

export 把项目加入环境变量中

export PATH=$PATH:要加入环境变量的路径 
# 实例,将home加入环境变量中 
export PATH=$PATH:/home 
source 通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录 

unset 取消变量

# 定义变量 
a="hello" 
echo $a 
输出:hello 
 
# 取消变量 
unset a 
echo  $a 
输出:   #输出是空的,因为变量a不存在了 

数组变量

# 定义数组 
array=(1 2 3 4 5) 
 
# 引用数组,并输出数组所有值 
echo ${array[@]}  
echo ${array[*]}  
 
# 取出数组第三个值 
echo ${array[2]} 
 
# 取出数组最后一个值 
echo ${array[-1]} 
 
# 将命令赋值给数组 
array=(`ls`) 
echo array  # 你在哪一层目录将ls赋值给array的,就会显示那一层ls下的所有文件 
数组使用*和@的区别
# 加上双引号时 
 array=(1 2 3);for line in "${array[@]}";do echo $line; done 
输出: 
1 
2 
3 
array=(1 2 3);for line in "${array[*]}";do echo $line; done 
输出: 
1 2 3 
 
# 不加上双引号时 
array=(1 2 3);for line in ${array[@]};do echo $line; done 
输出: 
1 
2 
3 
array=(1 2 3);for line in ${array[*]};do echo $line; done 
输出: 
1 
2 
3 
  • 由此可见当加上双引号后echo ${array[@]} 是以数组的方式一次输出一个值,echo ${array[*]}是以字符串的方式拼接成一个值输出。
  • 不加上双引号两者是没有区别的

特殊符号

  • " "双引号用于括起一段字符串值,支持$var形式的变量替换
  • ' '单引号也表示其内容是字符串值,不支持转义
  • ` ` 反引号的作用就是将反引号里面的内容当做是命令在执行。必须是shell真的存在的命令
a=`ls` 
echo a  # 相当于是ls命令了 
# 将ls命令赋值给a  
  • $(ls) 表示执行ls后的结果。与` `类似。不过可以嵌套

  • 反引号和$()的区别:
    1.容易和单引号混淆;
    2.在多层嵌套使用时必须额外的跳脱(\`)处理,而使用$(ls)就没有这样的问题。

  • \ 转义符

echo  -e "a\nbb"   
输出: 
  a 
  bb 
 
参数 
# -e 开启转义 
# \ 转译符 
# \n 换行 
# \b 删除它前面一个字符 
# \a 发出警告声 
 
echo -e "a\bb" 
输出:b 
 
  • $(())对变量进行操作,+ - * / % & | !等
a=1 
b=2 
echo $((a+b)) 
输出:3 
 
echo $((2+3)) 
输出:5 
  • (()) 是整数扩展,把里面的变量当做整数去处理
a=1 
b=2 
 
(($a>$b)); echo $?  # 表示1是否大于2 返回的是false 
输出:1 
 
(($a<$b)); echo $? # 表示1是否小于2 返回的是true 
输出:0 
说明:在shell中0表示true,其他非0代表false 
 
((a=a+b)); echo $a 
输出:3 
 
((a++)); echo $a 
输出:2 
 
$(()) 和 (())的区别 
$是变量的标志 
(())是运算的标志 
$((运算))代表运算结果 
  • seq 生成一个序列
array=(`seq 1 10`) 
echo array 
输出:1 2 3 4 5 6 7 8 9 10 
  • ({1...10}) 等价于 seq 1 10,表示1到10
  • ${ } 用来作变量替换用的,一般情况下,$var 与 ${var} 并没有啥不一样。但是用 ${ } 会比较精确的界定变量名称的范围。
  • $?命令执行返回都结果,是true 还是 false

字符串操作

切片

s="hello shell" 
echo ${s:6} # 下标第6个位置开始取字符串hello shell ,下标从0开始哦 
输出:shell  

切片取字符串长度

s="hello shell" 
echo ${s:6:3}  # 表示从第6个位置开始取长度3个字符 
输出:she 

计算字符串长度

s="hello shell" 
echo ${#s} 
输出:11 

掐头去尾 (非贪婪模式,只取第一个匹配到的字符)

s="hello world" 
 
echo ${s#hello}  # 掐头 把hello 去掉 使用 # 
输出:world 
 
echo ${s#*w}  #  *表示匹配任意字符,只要碰到w前面的所有字符都去掉(包括w) 
输出:orld 
 
echo ${s%world} # 去尾,使用 % 
输出:hello 
 
echo ${s%w*} # 去尾,只要碰到w后面的所有字符都去掉(包括w) 
输出:hello 

掐头去尾(贪婪模式,一直匹配到最后一个对应的字符)

s="hello shell" 
echo ${s##*s} # 掐头 使用 ## 
echo ${s%%s*}  # 去尾 使用 %% 

字符串替换 (非贪婪模式)

s="hello shell" 
echo ${s/shell/python} # 表示把shell替换成python 使用 / 
输出:hello python  

字符串替换 (贪婪模式)

s="hello shell" 
echo ${s//l/x} # 表示把所有l替换成x 使用 // 
输出:hexxo shexx  

加上双引号不忽略前面都空格

s="hello shell" 
echo "${s#hello}" 
输出: shell  # 注意shell前面有个空格的 

字符串比较

  • [ string1 = string2 ] 如果两个字符串相同,则结果为真
s1="abc" 
s2="bcd" 
[ "$a" = "$b"]; echo $? 
输出:false 
  • [ string1 != string2 ] 如果两个字符串不相同,则结果为真
  • [ -n string ] 如果字符串不是空, 则结果为真
  • [ -z string ] 如果字符串是空,则结果为真
  • [[ "xxx" == x* ]] 在表达式中表示0或者多个字符
s="hello" 
 [[ "$s" == h* ]] ; echo $? # 表示$s 是否等于以h开头后面任意字符的字符串 
输出:0 
  • [[ "xxx" == x?? ]] 在表达式中表示单个字符
s="hello" 
 [[ "$s" == m?? ]] ; echo $? # 表示$s 是否等于以m开头的单个字符串 
输出:1 
  • [[ ]]是[ ]的扩展语法,在老的sh里并不支持,推荐使用[ ]
  • 字符串比较最好都加上双引号
# 如果不加上双引号这里有个坑 
a=""; 
b="abc"; 
[ $a = $b ]; echo $? 
输出:-bash: [: =: unary operator expected 
 
# 加上双引号后 
[ "$a" = "$b" ]; echo $? 
输出:1 # 表示不成立 
 
说明:如果字符串为空时,不加上双引号会引发错误,所以建议在对字符串操作时都加上双引号 

算数判断

  • [ 2 -eq 2 ] 相等
  • [ 2 -ne 2 ] 不等
  • [ 3 -gt 1 ] 大于
  • [ 2 -ge 2 ] 大于等于
  • [ 3 -lt 4 ] 小于
  • [ 2 -le 2 ] 小于等于
  • (())也可以表示算术比较,((10>=8)), ((100==100)) 推荐使用这种

逻辑运算

  • 逻辑与
# -a 表示逻辑与 
[ 2 -ge 1 -a 3 -ge 4 ]; echo $?  # 表示2大于1 并且 3大于4 
输出:1 # 代表条件不成立 
 
扩展语法 
# && 表示逻辑与 
[[ 2 -ge 1 && 3 -ge 4 ]]; echo $?  # 表示2大于1 并且 3大于4 
输出:1 # 代表条件不成立 
  • 逻辑或
# -o 表示逻辑或 
[ 2 -ge 1 -o 3 -ge 4 ]; echo $?  # 表示2大于1 或者 3大于4 
输出:0 # 代表条件成立 
 
扩展语法 
# || 表示逻辑或 
[[ 2 -ge 1 || 3 -ge 4 ]]; echo $?  # 表示2大于1 或者 3大于4 
输出:0 # 代表条件成立 
  • 逻辑非
# ! 表示逻辑非 
[ ! 2 -ge 1 ]; echo $?  # 表示 取反 
输出:1 # 代表条件不成立 

内置判断

  • -e file 如果文件存在,则结果为真
  • -d file 如果文件是一个子目录,则结果为真
  • -f file 如果文件是一个普通文件,则结果为真
  • -r file如果请文件是可读,则结果为真
  • -w file 如果请文件是可写,则结果为真
  • -x file 如果请文件是可执行,则结果为真
  • -s file 如果文件的长度不为0,则结果为真
# 判断是不是目录 
[ -d 文件名 ]; echo $? 

逻辑控制

if结构

  • if [ 条件 ] ; then ...; fi
  • if [ 条件] ; then ...; else ...fi
  • if [ 条件 ]; then ... ; elif; then ... fi
  • 简单的逻辑可以使用 && || 去替代
  • 条件可以使用命令返回值代替
# 判断文件是否存在,如果文件存在打印exist,如果文件不存在打印 not exist 
if [ -e test ]; then echo exist; else echo not exist; fi   
 
# 判断ls命令下是否有文件 
if ls test; then echo exist; else not exist; fi # 如果ls命令下存在文件则输出exist否则输出not exist 
 
# 简写 如果执行ls命令返回的是true,则输出exist,如果返回的是false 则输出not exist 
ls test && echo exist || echo not exist   
 
#  简写并不完全等价于if逻辑判断,这里有个坑。例如 
ls test || echo exist && echo not exist  # 这样的话会输出 not exist 
 
# 原因,执行||后面的语句 时, 只有当||前面的语句返回的是false时才会执行,如果是true就不会执行了,直接执行 && 后面的语句了。 
 
# 试试下面的例子 
echo "1" && echo "2" || echo "3" && echo "4" || echo "5" || echo "6" && echo "7" && echo "8" || echo "9" 
 

for循环

  • for (( i=0; i<10; i++)) ;
    do
    ....
    done

  • for x in ${array[@]};
    do ... ;
    done

# 普通循环 
for (( i=0; i<10; i++ )); 
do 
echo $i; # 打印 i 
done 
 
# 循环打印数组 
array=(1 2 3 4 5) 
for ((i=0; i<${#array[@]}; i++)); do echo ${array[i]}; done 
 
# for 遍历循环 
array=(1 2 3 4 5) 
for x in ${array[@]}; do echo $x; done 
 
# 循环取目录下的所有文件 
for x in *; do echo $x; done # * 代表当前目录的所有文件 或者 用`ls`(但是用`ls`有个坑 如果文件名称中间有空格会被当成两个文件) 

while循环

i = 0 
while [ $i -lt 3 ]; 
do  
echo $i; 
(( i++ )); 
done 
输出: 
0  
1 
2 

退出循环

  • return 函数返回
  • exit 脚步退出
  • break 退出当前循环,默认为1
  • break 2 退出两层循环
  • continue 跳过当前的循环,进入下一次循环
  • continue 2 跳到上层循环的下次循环中

Shell运行环境概念

  • bash 是一个进程
    • bash下还可以再重新启动一个shell,这个shell是子shell, 原shell会复制自身给它
    • 在子shell中定义的变量,会随着子shell的消亡而消失,相当于python中函数中定义的变量,随着函数的执行完成函数中的变量也消失了
    • 父程序的自定义变量是无法在子程序内使用的。但是通过 export 将变量变成环境变量后,就能够在子程序下面应用了
  • ( )在子shell中运行,外层是取不到( )中的变量的
# 子shell中运行 
a=2 
>(a=1; echo $a); echo $a 
输出: 
1 
2 
可以看出在外层中echo $a 不会因为在(a=1)重新新赋值而受到影响。 
 
# 设置变量并可以在子程序中使用 
>name=jack 
>bash <==进入到所谓的子程序 
>echo $name <==子程序:再次的 echo 一下; 
  <==嘿嘿!并没有刚刚设置的内容喔! 
>exit <==子程序:离开这个子程序 
>export name 
>bash <==进入到所谓的子程序 
>echo $name <==子程序:在此执行! 
jack <==看吧!出现设置值了! 
>exit <==子程序:离开这个子程序 
  • { } 当前shell中运行
  • $?命令执行返回都结果,是true 还是 false
    • 任何命令执行都会有一个返回值
    • 0表示正确
    • 非0表示错误
true 
echo $? 
输出: 0 
 
false  
echo $? 
输出:1 
 
ls 
echo $? 
输出: 0 
  • $$ 当前bash(脚本)执行的pid

  • & 后台执行

for ((i=0; i<5; i++)); do echo $i; sleep 2; done & 
# sleep 休眠2秒 
# &在后台运行 
  • $! 运行在后台的最后一个进程PID
  • jobs 查看后台运行的任务现在执行的状态和任务序号
  • bg num 当使用crlt + z 把前台任务切换到后台时,会暂停任务,使用 bg + 任务的序号 可以继续执行任务
    image.png
  • fg num 把后台任务显示到前台,有这样一种场景,当你使用vim编辑文本时,想先退出去做其他的操作,这时可以使用crlt + z把vim切到后台,等其他操作完成时 使用 fg + 任务序号 在把vim切换到前台,继续操作。

vim常用快捷键

  • i进入编辑模式
  • v 剪切复制模式
  • esc 退出编辑模式

剪切复制模式下

  • y 复制
  • d 剪切
  • p 粘贴

esc 模式下

  • gg 跳到文件头
  • G 跳到文件尾
  • nG 跳到指定行 n代表行号
  • dd 删除整行

冒号模式下输入命令 在esc下按 shift + : 进入冒号命令行模式

  • q 退出不保存
  • q! 强制退出不保存
  • wq 保存退出
  • wq! 强制保存退出
  • set nu 显示行号
  • set nonu 不显示行号
  • /string 搜索指定字符串

Shell 输入输出

  • read用来读取输入,并赋值给变量,相当于python的input
# 让用户输入内容 
read re 
如输入:abc 
echo $re 
输出 abc 
 
# 有提示信息的输入 
read -p "请输入内容" re; echo $re # -p 后面可以加提示信息 
 
# 使用whlie循环读取文件的内容 
while read line; do echo $line; done < txet(文件名) 
 
# 将输入的内容写入到文件中 
while read line; do echo $line; done > txet(文件名) 
  • echo,printf 可以简单输出变量
  • > file 将输出重定向到另一个文件,等价于tee
# 将内容写入到文件中,会覆盖原有的内容 
echo "hello shell" > txt 
  • >>表示追加等价于tee -a
# 将内容追加写入到文件中,不会覆盖原有的内容 
echo "hello python" >> txt 
  • < file 输入重定向
# 将原本是用键盘输入的重定向给了txt文件,变成了以文件的内容输入, 
# 所以就是将文件的内容输出 
read x < txt # read一次只读取一行 
  • | 表示管道,也就是前一个命令的输出作为下一个命令的输入
  • tee 会同时将数据流分送到文件去与屏幕
tee [-a] file 
选项与参数: 
-a :以累加 (append) 的方式,将数据加入 file 当中! 
 
# 实例 
echo "abcd" | tee a.txt 
abcd 
cat a.txt  
abcd 

文件描述符

# 将执行命令时提示错误的信息输入到文件中 
# 如ls一个不存在到目录会提示:No such file or directiry 
ls aaa > txt 2>&1 
# 说明:当我们执行一个命令的时候会有一个true的状态和false的状态, 
# 当命令执行成功返回true时会重定向到1中(1现在被我改成了txt文件) 
# 当执行不成功返回false时会重定向2中(2默认是输出到命令窗口) 
# >&1的作用就是告诉机器把错误的信息不要打印到命令窗口了,你跟我打印到txt文件中去 

输入/输出

printf格式输出

# 格式 
printf '打印格式' 实际内容 
 
%ns 那个 n 是数字, s 代表 string ,亦即多少个字符; 
%ni 那个 n 是数字, i 代表 integer ,亦即多少整数; 
%N.nf 那个 n 与 N 都是数字, f 代表 floating (浮点), 
假设我共要十个位数,但小数点有两位,即为 %10.2f 啰! 
 
# 实例 
printf '%s %i %.2f\n' $(echo "abc" 123 10.89) 
abc 123 10.89 
 

函数

  • $0 表示执行的程序,是相对于执行目的的路径
  • $1,$2,$3 分别表示第几个参数,shell默认只能支持9个参数,使用shift可以传递更多的参数。
  • $@,$* 表示所有的参数,不包含$0。
  • ${#*}和${#@}表示位置参数的个数。
  • 通过${*:1:3},${*:$#}来表示多个参数。

实例

#!/bin/bash 
func(){ 
        echo '$1='$1 
        echo '$2='$2 
        echo '$@='$@ 
        echo '$*='$* 
        echo '$0='$0 
        echo '${#*}'=${#*} 
        echo '${#@}'=${#@} 
        echo '"$*"'="$*" 
        echo '"$@"'="$@" 
        echo '${*:1:3}='${*:1:3} 
        echo '${*:$#}='${*:$#} 
        echo '${*:1:1}='${*:1:1} 
} ; 
func 1 2 3 4 5 6 7 8 9 
输出: 
$1=1 
$2=2 
$@=1 2 3 4 5 6 7 8 9 
$*=1 2 3 4 5 6 7 8 9 
$0=func.sh 
${#*}=9 
${#@}=9 
"$*"=1 2 3 4 5 6 7 8 9 
"$@"=1 2 3 4 5 6 7 8 9 
${*:1:3}=1 2 3 
${*:$#}=9 
${*:1:1}=1 
 
$@ $* "$@" "$*"的区别
#!/bin/bash 
count=0 
f(){ 
        echo '$@'=$@ 
        for i in $@; do echo ${i}; ((count++)); done; 
        echo 'count='${count} 
} 
f 1 2 '3 "4 5" 6' 7 "8 9" 
 
输出:      
$@=1 2 3 "4 5" 6 7 8 9 
1 
2 
3 
"4 
5" 
6 
7 
8 
9 
count=9 
========================================== 
 
# "$@" 带双引号 
count=0 
f(){ 
        echo '"$@"'="$@" 
        for i in "$@";do echo ${i}; ((count++)); done; 
        echo 'count='${count} 
} 
f 1 2 '3 "4 5" 6' 7 "8 9" 
输出: 
"$@"=1 2 3 "4 5" 6 7 8 9 
1 
2 
3 "4 5" 6 
7 
8 9 
count=5 
========================================== 
 
# $* 不带双引号 
count=0 
f(){ 
        echo '$*'=$* 
        for i in $*;do echo ${i}; ((count++));done; 
        echo 'count='${count} 
} 
f 1 2 '3 "4 5" 6' 7 "8 9" 
输出: 
$*=1 2 3 "4 5" 6 7 8 9 
1 
2 
3 
"4 
5" 
6 
7 
8 
9 
count=9 
========================================== 
 
# "$*"带双引号 
count=0 
f(){ 
        echo '"$*"'="$*" 
        for i in "$*";  do echo ${i}; ((count++)); done; 
        echo 'count='${count} 
} 
f 1 2 '3 "4 5" 6' 7 "8 9" 
输出: 
"$*"=1 2 3 "4 5" 6 7 8 9 
1 2 3 "4 5" 6 7 8 9 
count=1 
 

记得使用"$@",不要直接使用$@

使用外部文件执行函数时,通过外部函数传参

  1. 创建一个test.sh文件vim test.sh
  2. 在文件中写入如下函数:
#!/bin/bash                                                      
f() 
{ 
    name=$1 
    echo "${name}" 
} 
f $1 
  1. 执行文件
$ bash test.sh abc(参数) 
abc 

执行方式

  • chmod u+x test.sh; ./test.sh 在当前shell中执行
  • bash test.sh 开启一个子shell执行
  • source test.sh 在当前shell中执行,同./test.sh

DBUG代码

  • bash -x 读取代码中的每一句,并执行,可以方便的看到每次的执行过程。
#!/bin/bash 
# bash_dbug函数 
$ cat bash_dbug.sh 
f(){ 
    for i in $(seq 1 5); do echo ${i}; done 
} 
f 
$ bash -x bash_dbug.sh  # dbug模式执行函数 
+ f    # 先执行f函数 
++ seq 1 5  # 执行seq 
+ for i in $(seq 1 5) # 循环 
+ echo 1  # 输出 
1 
+ for i in $(seq 1 5) # 第二次循环 
+ echo 2 
2 
+ for i in $(seq 1 5) # 第三次循环... 
+ echo 3 
3 
+ for i in $(seq 1 5) 
+ echo 4 
4 
+ for i in $(seq 1 5) 
+ echo 5 
5 
  • set -x 开启dbug模式,可以放在脚本的开头使用
#! /bin/bash 
# set_dbug的函数 
$ cat set_dbug.sh 
   
set -x  # 开启dbug 
f(){ 
    for i in $(seq 1 5); do echo ${i}; done 
} 
f 
 
$ ./set_dbug.sh   # 执行函数 
+ f 
++ seq 1 5 
+ for i in $(seq 1 5) 
+ echo 1 
1 
+ for i in $(seq 1 5) 
+ echo 2 
2 
+ for i in $(seq 1 5) 
+ echo 3 
3 
+ for i in $(seq 1 5) 
+ echo 4 
4 
+ for i in $(seq 1 5) 
+ echo 5 
5 
 
  • set +x 关闭dbug模式
  • set 可以进行局部调试,在需要调试的代码之前加上“set -x”,需要调试的代码之后加上“set +x”即可

发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

docker常用命令知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。