shell的使用
1 2 3 | #!/bin/bash # This is a very simple example echo "Hello World" |
-
#!/bin/bash
表明该文件是一个BASH
程序,需要由/bin
目录下的bash
程序来解释执行。BASH
这个程序一般是存放在/bin
目录下,如果你的 Linux 系统比较特别,bash
也有可能被存放在/sbin 、/usr/local/bin 、/usr/bin 、/usr/sbin 或 /usr/local/sbin
这样的目录下;如果还找不到,你可以用"locate bash" "whereis bash"
这三个命令找出 bash 所在的位置;如果仍然找不到,那你可能需要自己动手安装一个BASH
软件包了 -
第二行的 “
# This is a ...
“ 就是BASH
程序的注释,在BASH
程序中从#
号(注意:后面紧接着是!
号的除外)开始到行尾的多有部分均被看作是程序的注释 - 三行的
echo
语句的功能是把echo
后面的字符串输出到标准输出中去 - 需要注意的是
BASH
中的绝大多数语句结尾处都没有分号。
显式指定 BASH
去执行
1 2 3 4 5 | $ bash hello $ sh hello 这里 sh 是指向 bash 的一个链接, lrwxrwxrwx 1 root root 4 Aug 20 05:41 /bin/sh -> bash |
隐式执行
1 2 | $ chmod a+x hello $ ./hello |
关于输入、输出和错误输出
1 | 在字符终端环境中,标准输入/标准输出的概念很好理解。输入即指对一个应用程序 或命令的输入,无论是从键盘输入还是从别的文件输入;< br >输出即指应用程序或命令产生的一些信息;与 Windows 系统下不同的是,Linux 系统下还有一个标准错误输出的概念,这个概念主要是为程序调试和系统维护目的而设置的,错误输出于标准输出分开可以让一些高级的错误信息不干扰正常的输出 信息,从而方便一般用户的使用 |
在 Linux 系统中:标准输入(stdin
)默认为键盘输入;标准输出(stdout
)默认为屏幕输出;标准错误输出(stderr
)默认也是输出到屏幕.
在 BASH
中使用这些概念时,
- 将标准输出表示为
1
- 将标准错误输出表示为
2
1 2 | $ ls > ls_result $ ls -l >> ls_result |
上面这两个命令分别将 ls
命令的结果输出重定向到 ls_result
文件中和追加到 ls_result
文件中,而不是输出到屏幕上。
>
就是输出(标准输出和标准错误输出)重定向的代表符号>>
则表示不清除原来的而追加输入
下面再来看一个稍微复杂的例子:
1 | $ find /home -name lost* 2> err_result |
这个命令在 >
符号之前多了一个 2
,2>
表示将标准错误输出重定向到err_result
中
要想让标准错误输出和标准输入一样都被存入到文件中,那该怎么办呢?看下面这个例子:
1 | $ find /home -name lost* > all_result 2>& 1 |
上面这个例子中将首先将标准错误输出也重定向到标准输出中,再将标准输出重定向到 all_result 这个文件中。这样我们就可以将所有的输出都存储到文件中了
如果那些出错信息并不重要,下面这个命令可以让你避开众多无用出错信息的干扰:
1 | $ find /home -name lost* 2> /dev/null |
再试验一下如下几种重定向方式,看看会出什么结果,为什么?
1 2 3 | $ find /home -name lost* > all_result 1>& 2 $ find /home -name lost* 2> all_result 1>& 2 $ find /home -name lost* 2>& 1 > all_result |
shell 针对参数已经有设定好一些变量名称,对应如下:
1 2 | /path/to/scriptname opt1 opt2 opt3 opt4 $0 $1 $2 $3 $4 |
执行的脚本档名为 $0
这个变量,第一个接的参数就是 $1
后面类推
特殊变量
1 2 3 | $# :代表后接的参数『个数』,以上面为例这里显示为『 4 』; $@ :代表『 "$1" "$2" "$3" "$4" 』之意,每个变量是独立的(用双引号括起来); $* :代表『 "$1c$2c$3c$4" 』,其中 c 为分隔字符,默认为空格键, 所以本例中代表『 "$1 $2 $3 $4" 』之意。 |
Shell
编程中,使用变量无需事先声明,同时变量名的命名须遵循如下规则:
- 首个字符必须为字母(a-z,A-Z) 或者_
- 中间不能有空格,可以使用下划线(_)
- 不能使用其他标点符号
- 需要给变量赋值时,可以这么写:
1 | 变量名=值 |
要取用一个变量的值,只需在变量名前面加一个$ ( 注意: 给变量赋值的时候,不能在”=”两边留空格 )
1 2 3 4 5 6 7 8 9 | #!/bin/bash # 对变量赋值: a="hello world" #等号两边均不能有空格存在 # 打印变量a的值: echo "A is:" $a 有时候变量名可能会和其它文字混淆,比如: num=2 echo "this is the $numnd" |
上述脚本并不会输出this is the 2nd
. 这是由于shell会去搜索变量numnd
的值,而实际上这个变量此时并没有值。这时,我们可以用花括号来告诉shell
要打印的是num
变量:
1 2 3 4 5 6 7 8 | num=2 echo "this is the ${num}nd" 其输出结果为:this is the 2nd 注意花括号的位置: num=2 echo "this is the {$num}nd" 其输出结果为:this is the {2}nd |
需要注意shell的默认赋值是字符串赋值。比如:
1 2 3 4 | var=1 var=$var+1 echo $var 打印出来的不是2而是1+1。 |
为了达到我们想要的效果有以下几种表达方式:
1 2 3 4 5 | let "var+=1" var="$[$var+1]" ((var++)) var=$(($var+1)) var="$(expr "$var" + 1)" |
if
表达式如果条件为真,则执行then后的部分:
1 2 3 4 5 6 7 | if ....; then .... elif ....; then .... else .... fi |
1 | 大多数情况下,可以使用测试命令来对条件进行测试,比如可以比较字符串、判断文件是否存在及是否可读等等 |
通常用[ ]
来表示条件测试,==注意这里的空格很重要,要确保方括号前后的空格==
1 2 3 4 | [ -f "somefile" ] :判断是否是一个文件 [-d "Directory"] : 判断目录是否存在 [ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限 [ -n "$var" ] :判断$var变量是否有值 |
下面是一个简单的if语句:
1 2 3 4 5 6 7 | #!/bin/bash if [ ${SHELL} == "/bin/bash" ]; then echo "your login shell is the bash (bourne again shell)" else echo "your login shell is not bash but ${SHELL}" fi |
1 | case表达式可以用来匹配一个给定的字符串,而不是数字(可别和php语言里的switch...case混淆) |
1 2 3 | case ... in do something here ;; esac |
当给程序输入start
时,显示service is running!
;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/bin/bash read -p "请输入参数:" var case $var in start) echo service is running ;; stop) echo service is stoped ;; reload) echo service is reload ;; *) echo xxxxx ;; esac |
在shell中,可以使用如下循环:
1 2 3 4 | while [ condition ] <==中括号内的状态就是判断表达式 do <== do 是循环的开始! 程序段落 done <== done 是循环的结束 |
只要测试表达式条件为真,则while
循环将一直运行。关键字break
用来跳出循环,而关键字continue
则可以跳过一个循环的余下部分,直接跳到下一次循环中。
for循环会查看一个字符串列表(字符串用空格分隔),并将其赋给一个变量:
1 2 3 | for var in ....; do .... done |
下面的示例会把A B C分别打印到屏幕上:
1 2 3 4 5 | #!/bin/bash for var in A B C; do echo "var is $var" done |
下面是一个实用的脚本showrpm
,其功能是打印一些RPM包的统计信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/bin/bash # list a content summary of a number of RPM packages # USAGE: showrpm rpmfile1 rpmfile2 ... # EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm for rpmpackage in "$@"; do if [ -r "$rpmpackage" ];then echo "=============== $rpmpackage ==============" rpm -qi -p $rpmpackage else echo "ERROR: cannot read file $rpmpackage" fi done |
这里出现了第二个特殊变量$@
,该变量包含有输入的所有命令行参数值。如果你运行showrpm openssh.rpm w3m.rpm webgrep.rpm
,那么 $@
就包含有 3 个字符串,即openssh.rpm
, w3m.rpm
和 webgrep.rpm
先来看一个例子,假设在当前目录下有两个jpg文件:mail.jpg
和tux.jpg
1 2 3 4 5 | #!/bin/bash echo *.jpg 运行结果为: mail.jpg tux.jpg |
引号(单引号和双引号)可以防止通配符*
的匹配:
1 2 3 4 5 6 7 | #!/bin/bash echo "*.jpg" echo '*.jpg' 其运行结果为: *.jpg *.jpg |
其中单引号更严格一些,它可以防止任何特殊字符匹配:
1 2 3 4 5 6 7 8 9 | #!/bin/bash echo $SHELL echo "$SHELL" echo '$SHELL' 运行结果为: /bin/bash /bin/bash $SHELL |
此外还有一种防止这种扩展的方法,即使用转义字符——反斜杆:\
1 2 3 4 5 | echo \*.jpg echo \$SHELL 输出结果为: *.jpg $SHELL |
如果你写过比较复杂的脚本,就会发现可能在几个地方使用了相同的代码,这时如果用上函数,会方便很多。函数的大致样子如下:
1 2 3 | function fname() { 程序段 } |
函数没有必要声明。只要在执行之前出现定义就行
那个 fname
就是我们的自定义的执行指令名称,而程序段就是我们要他执行的内容了。 要注意的是,因为 shell script
的执行方式是由上而下,由左而史, 因此在 shell script
当中的 function
的设定一定要在程序的最前面, 这样才能够在执行时被找找到可用的程序段
我们自定义一个名为 printit 的函数来使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #!/bin/bash PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH function printit(){ echo -n "Your choice is " # 加上 -n 可以不断行继续在同一行显示 } echo "This program will print your selection !" case $1 in "one") printit; echo $1 | tr 'a-z' 'A-Z' # 将参数做大小写转换! ;; "two") printit; echo $1 | tr 'a-z' 'A-Z' ;; "three") printit; echo $1 | tr 'a-z' 'A-Z' ;; *) echo "Usage $0 {one|two|three}" ;; esac |
以上面的例子来说,做了一个函数名称为 printit
,所以,当我在后续的程序段里面, 叧要执行 printit
的话,就表示我的 shell script
要去执行『 function printit .... 』
里面的那几个程序段落
另外, function
也是拥有内建变量, 他的内建变量与 shell script
很类似, 函数名称代表示 $0
,而后续接变量也是以 $1
, $2
… 来取代的, 这里很容易搞错, 因为『 function fname() { 程序段 } 』
内的 $0
, $1
… 等等与 shell script
的 $0
是不同的。
以上面程序来说,假如我执行:『 sh test.sh one 』
这表示在 shell script
内的 $1
为 “one” 这个字符串。但是在 printit()
内的 $1
则不这个 one
无关。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | [root@www scripts]# vi sh12-3.sh #!/bin/bash PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH function printit(){ echo "Your choice is $1" # 这个 $1 必须要参考底下指令的下达 } echo "This program will print your selection !" case $1 in "one") printit 1 # 请注意, printit 指令后面还有接参数! ;; "two") printit 2 ;; "three") printit 3 ;; *) echo "Usage $0 {one|two|three}" ;; esac |
在上面的例子当中,如果你输入『 sh sh12-3.sh one 』
就会出现『 Your choice is 1 』
的字样, 为什么是 1 呢?因为在程序段落当中,我们是写了『 printit 1 』
那个1
就会成为 function
当中的 $1
最简单的调试方法当然是使用echo
命令。你可以在任何怀疑出错的地方用echo
打印变量值,这也是大部分shell
程序员花费80%的时间用于调试的原因。Shell
脚本的好处在于无需重新编译,而插入一个echo
命令也不需要多少时间。
1 2 3 4 5 | [root@www ~]# sh [-nvx] scripts.sh 选项不参数: -n :不要执行 script,仅查询语法的问题; -v :再执行 sccript 前,先将 scripts 的内容输出到屏幕上; -x :将使用到的 script 内容显示到屏幕上,这是很有用的参数! |
范例一:测试 sh16.sh 有无语法的问题?
1 2 | [root@www ~]# sh -n sh16.sh # 若语法没有问题,则不会显示任何信息! |
范例二:将 sh15.sh 的执行过程全部列出来~
1 2 3 4 5 6 7 8 9 10 11 12 13 | [root@www ~]# sh -x sh15.sh + PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/root/bin + export PATH + for animal in dog cat elephant + echo 'There are dogs.... ' There are dogs.... + for animal in dog cat elephant + echo 'There are cats.... ' There are cats.... + for animal in dog cat elephant + echo 'There are elephants.... ' There are elephants.... |
nginx日志按日期自动切割脚本如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #nginx日志切割脚本 #!/bin/bash #设置日志文件存放目录 logs_path="/usr/local/nginx/logs/" #设置pid文件 pid_path="/usr/local/nginx/nginx.pid" #重命名日志文件 mv ${logs_path}access.log ${logs_path}access_$(date -d "yesterday" +"%Y%m%d").log #向nginx主进程发信号重新打开日志 kill -USR1 `cat ${pid_path}` crontab 设置作业 0 0 * * * bash /usr/local/nginx/nginx_log.sh
这样就每天的0点0分把nginx日志重命名为日期格式,并重新生成今天的新日志文件。 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架