gnosis of shell 深入理解shell编程
- shell中if while等后必须接命令,利用命令的exit code作为判断,而其他高级语言是利用return值作为判断
对于非命令变量,可以利用test or [ ] or [[ ]] 进行判断
如果确实需要用命令或函数的返回值作为判断,可以如下
function p(){ echo $$ exit 55 } if ! [ `p` ];then echo bbbb else echo pppppppppppp fi
利用命令替换获取函数p的返回值(由echo提供),而后利用 [ ] 进行判断
在执行函数p的时候,没有因为exit 55 退出整个脚本,而是打印了pppppppp,所以据此判断shell中命令引用是开辟了一个新的shell进程来执行命令引用,所以命令引用中的exit不会影响到父shellfunction p(){ echo $$ exit 55 } if ! p;then echo bbbb else echo pppppppppppp fi
直接用函数p的exit code : 55进行判断,但是函数p中包含了exit,结束了当前shell进程,所以整个shell脚本退出了,故此种方式函数p的执行是在当前shell进程,跟命令引用是不同的
所以改为returnfunction p(){ echo $$ return 55 } if ! p;then echo bbbb else echo pppppppppppp fi
函数返回值没有变量接收,直接打印了,exit code 为 55 ,取反后,为真
-
直接ps只会显示当和当前shell关联的进程和其父进程
-
function b(){ echo $$ exit 55 } echo 'current shell PID: '$$ p=`b` echo echo 'function b PID: '$p echo "ps"
命令引用后在函数b中$$依然为 20933,难道命令引用没有开辟子shell吗?
$ 扩展为shell的进程ID。在一个()
子shell,它扩展为当前shell的进程ID,而不是子shell。
shell中的函数凡是输出stdout & stderr的都是返回值,也可以利用echo明确返回
看如下例子:function b(){ "ps" exit 55 } echo 'current shell PID: '$$ p="`b`" echo echo -e 'function b ps result: \n'"$p" echo "ps"
明显看到执行函数b的命令引用时,多出 21476 这个子shell进程,这个时候不能通过变量 $ 进行判断了
function b(){ "ps" return 55 # substitution exit to return to prevent the whole script exit } echo 'current shell PID: '$$ echo b echo 'function b exit code: '$? echo "ps"
直接调用b,可以看到并没有开辟子shell执行函数b
function p(){ "ps" } echo -e '\033[7mscript ps\033[0m': "ps" echo echo -e '\e[7mfunction ps\e[0m': "p"
-
shell内置变量BASHPID与 $ 相同,显示当前shell的PID
#!/bin/sh echo $BASHPID function b(){ echo 'function b: '$BASHPID } b
直接调用函数b,显示相同的PID
#!/bin/sh echo $BASHPID function b(){ echo 'function b: '$BASHPID } `b`
上面出错的原因是`b`再次把函数b的返回值(echo 部分),作为命令执行了,相当于 在shell中输入 function b: $BASHPID
此时shell会把function当作可执行命令,故报错
#!/bin/sh echo $BASHPID function b(){ echo 'function b: '$BASHPID } echo `b`
直接把函数 b 的返回值用echo打印
-
BASH_SUBSHELL变量可以查看从当前shell开始的shell层数
当前shell显示为0,最顶级
echo 'current shell:' $BASH_SUBSHELL function p(){ echo 'inner function p:' $BASH_SUBSHELL } function b(){ echo "`p`" echo 'function b:' $BASH_SUBSHELL } echo "`b`"
-
shell脚本进程名
reap.sh#!/bin/env sh echo $BASHPID for((;;));do echo `basename $0` sleep 1 done
#!/bin/env sh 此种情况的脚本进程名:
./reap.sh运行的,脚本名进程名就是 env 后面的部分 sh 运行 脚本进程名是sh bash 运行脚本进程名是bash 因为sh or bash运行脚本 shebang将不再发挥作用
但是 basename $0永远是脚本文件名#!/bin/bash echo $BASHPID for((;;));do echo `basename $0` sleep 1 done
#!/binbash or #!/bin/sh 的脚本进程名./reap.sh 运行的脚本进程名就是脚本文件名 bash reap.sh or sh reap.sh 的脚本进程名则是 bash 或者 sh 但是basename $0 还同样是 脚本文件名
-
#!/bin/sh echo $BASHPID sleep 50 & sleep 10 chmod u+x garble.sh ./garble.sh
当前shell进程BASHPID 为 2129, garble.sh开辟的子shell BASHPID为6416
10s内pstree -p观察,garble.sh生成了两个sleep子进程 6417 & 6418
10s后pstree -p观察只剩下后台的sleep进程6417,挂靠在一号进程systemd下
#!/bin/sh echo $BASHPID sleep 50 & sleep 10 kill $!
可以改进脚本,kill $!可以杀掉脚本中最后一个运行的后台进程
再来看一种特殊效果#!/bin/sh echo $BASHPID commence=`date +'%Y-%m-%d %H:%M:%S'` echo $commence function uiop(){ sleep 10 & } `uiop` sleep 15 kill $! closure=`date +'%Y-%m-%d %H:%M:%S'` echo $closure commence_seconds=$(date --date="$commence" +%s) closure_seconds=$(date --date="$closure" +%s) echo -e "\e[7;36mscript elapsed: $[closure_seconds-commence_seconds]\e[0m"
前10S的状态
15S后台的状态
函数uiop内sleep是后台运行的,函数uiop结束后,函数uiop几乎在瞬间就结束了,sleep就直接挂在systemd下,但是函数uiop不是在后台运行的,所以garble.sh (28546)脚本主进程会等sleep(28549)结束后才向下执行#!/bin/sh echo $BASHPID commence=`date +'%Y-%m-%d %H:%M:%S'` echo $commence function uiop(){ sleep 10 echo $BASH_SUBSHELL } echo `uiop` sleep 15 kill $! closure=`date +'%Y-%m-%d %H:%M:%S'` echo $closure commence_seconds=$(date --date="$commence" +%s) closure_seconds=$(date --date="$closure" +%s) echo -e "\e[7;36mscript elapsed: $[closure_seconds-commence_seconds]\e[0m"
前10s
后10s
sleep放在前台运行的话,就很好理解了,开辟子shell,运行函数
需要注意的是函数名为uiop,但是子shell进程名是和父shell相同,都是脚本名 -
#!/bin/sh echo $BASHPID for((;;));do echo `basename $0` sleep 5 done & sleep 10
前10s
后10s - ~
分析:前10s,for loop由于是后台执行,故开辟子shell(24626),和 reap.sh的sleep(24637)几乎同时运行,10s后,reap.sh(24625)结束,reap.sh(24626)被systemd收养
需要注意的是子shell默认进程名和父shell相同,都是脚本名 -
#!/bin/sh echo $BASHPID for((;;));do echo `basename $0` sleep 5 done & killall sleep kill $BASHPID
killall sleep & kill $BASHPID 都不能杀死 & 生成子shell进程 killall sleep只能杀死现存的sleep,而kill $BASHPID杀死了父shell,都与 & 生成的子shell无关
-
30958 reap.sh Terminated [root@perpetual zxc]# cat reap.sh #!/bin/sh echo $BASHPID for((;;));do echo `basename $0` sleep 5 done & killall `basename $0`
killall `basename $0`能杀死所有 reap.sh进程,这只适用于 ./reap.sh的运行方法 -
再考虑一个问题,如果脚本已经执行到了while中的后台任务,但在执行到killall命令之前按下了CTRL+C,这时由于没有执行killall,后台任务也将挂在新的脚本进程下。我们的目的是保证脚本终止,其内进程一定终止。所以我们需要对这种情况做出合理的处理。
#!/bin/sh trap "killall `basename $0`" SIGINT echo $BASHPID for((;;));do echo `basename $0` sleep 5 done & killall `basename $0`
如果使用bash reap.sh 或者 sh reap.sh则进程名为bash or sh , reap.sh是一个参数
这个时候要借助pkill的 -f选项 -f, --full use full process name to match#!/bin/sh trap "pkill -f `basename $0`" SIGINT echo $BASHPID for((;;));do echo `basename $0` sleep 5 done & pid=$! kill $pid sleep 5 pkill -f `basename $0`
第一个Terminated 是 kill $pid的
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律