shell的使用

第一个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
举例说明
输入、输出及标准错误输出主要用于 I/O 的重定向。先看这个例子:
1
2
$ ls > ls_result
$ ls -l >> ls_result

上面这两个命令分别将 ls 命令的结果输出重定向到 ls_result 文件中和追加到 ls_result 文件中,而不是输出到屏幕上。

  • >就是输出(标准输出和标准错误输出)重定向的代表符号
  • >> 则表示不清除原来的而追加输入

下面再来看一个稍微复杂的例子:

1
$ find /home -name lost* 2> err_result

这个命令在 > 符号之前多了一个 22> 表示将标准错误输出重定向到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特殊参数【命令行参数】

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)"
SHELL里的流程控制
If语句

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
case 语句
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
while/for循环

在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.rpmw3m.rpm和 webgrep.rpm

Shell里的一些特殊符号

先来看一个例子,假设在当前目录下有两个jpg文件:mail.jpgtux.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
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

SHELL脚本示例
脚本调试

最简单的调试方法当然是使用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日志重命名为日期格式,并重新生成今天的新日志文件。

  

posted @   鲸鱼的海老大  阅读(15)  评论(0编辑  收藏  举报
编辑推荐:
· 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 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示