Shell脚本(1)

将自己之前的shell脚本笔记搬到segmentfault,顺带复习一下shell基础。笔记大多是读<<跟老男孩学Linux运维:Shell编程实站>>时所记录,推荐想学shell的可以去看看。2019-1-26

shell脚本介绍

Shell介绍
Shell是一个命令解释器,作用是解释执行用户输入的命令程序等,用户每输入一条命令,Shell就执行解释一条。这种从键盘输入命令就可以得到回应的方式叫做交互的方式。

Shell存在于操作系统的最外层,负责与用户直接对话,把用户的输入解释给操作系统,并处理各种操作系统的输出结果,然后输出到屏幕给用户。

Shell脚本
当命令或程序语句不在命令行下执行,而是通过一个程序文件来执行,这个文件就叫做Shell脚本。
Shell脚本很适合用于处理纯文本类型的数据,而Linux系统中几乎所有的配置文件,日志文件(NFS,Rsync,Httpd,Nginx,LVS 等),以及绝大多数的启动文件都是纯文本类型的文件。因此,学好shell语言,就可以利用它在linux系统中发挥巨大的作业。

查看CentOS系统默认的Shell

[root@moli_linux1 ~]# echo $SHELL
/bin/bash
[root@moli_linux1 ~]# grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash

Shell脚本的创建和执行

Shell脚本的创建
一个规范的Shell脚本在第一行会指出由那个程序(解释器)来执行脚本的内容,这一行内容在Linux bash编程中一般为:

#!/bin/bash

或者

#!/bin/sh

注意:这一行必须位于每个脚本顶端的第一行,如果不是第一行则为脚本注释行。且shell脚本文件的后缀名通常以.sh结尾。

Shell脚本的执行

1. bash script-name(脚本的路径名字)或者sh script-name:这是当脚本文件没有可执行权限时常用的放法
2. path/script-name 或者./script-name:指在当前路径下执行脚本(脚本需要有可执行权限)。即需要将脚本文件的权限先修改为可执行,chmod +xscript-name 。然后通过脚本的绝对路径和相对路径就可以直接执行脚本。

shell脚本中的变量

什么是变量
变量是暂时存储数据的地方及数据标记,所存储的数据存在于内存空间中,通过正确地调用内存空间中变量的名字就可以取出与变量对应的数据。CentOS中变量的定义直接由一个赋值符号=就可以创建一个变量,而echo命令类似于其他语言的print命令,打印这个变量的值。

[root@moli_linux1 ~] name="laowan" #定义一个变量
[root@moli_linux1 ~] echo $name  #打印这个变量的值
laowan
[root@moli_linux1 ~] age=14
[root@moli_linux1 ~] echo $age
14

shell变量的特性
默认情况下,在bash shell 中是不会区分变量类型的。
变量类型
变量分为:环境变量(全局变量)和普通变量(局部变量)
环境变量可以在创建它们的shell以及其派生出来的任意子进程shell中使用。环境变量又分为自定义环境变量和bash内置的环境变量。
普通变量只能在创建它们的shell函数或shell脚本中使用。
提示:有三个命令可以查看系统中变量的值:set、env、declare。

  • set命令输出所有的变量;
  • env只显示全局变量;
  • declare命令输出所有的变量,函数,整数和已经导出的变量。
  • set -o命令显示bash shell的所有参数配置信息。

环境变量的定义
上面定义一个变量是在一个shell中直接使用赋值符号对这个变量进行创建,但是当打开一个新的子shell,打印这个变量却不能打印出来。这是因为我们创建的变量是一个普通变量,若想在其他子shell中使用这个变量,需要将这个变量设置为全局变量。而定义一个全局变量的方法如下:

export 变量名=value
或者
declare -x 变量名=value

第二个方法是将变量名添加到环境变量文件/etc/profile

编辑环境配置文件
vim /etc/profile
在文件最后面添加
export name="laowan"
保存,退出。重新加载文件
source /etc/profile
打印变量的值
echo $name
laowan

而CentOS中,全局环境变量的配置文件有三个,分别是:

  • /etc.profile
  • /etc/bashrc
  • /etc/profile.d #这是一个目录

当用户登录Linux系统时,首先会先加载/etc/profile全局环境变量文件,这是Linux系统默认的Shell主配置文件,其次会执行/etc/profile.d下的脚本文件,要做重启Linux系统后初始化或者显示某些内容,只需要把脚本文件放在/etc/progile.d下即可,最后才会执行bashrc文件。

设置登录提示符
Linux中可以设置登录之后,或者远程连接服务器打开的shell中显示登录提示符。设置登录提示内容的文件是/etc/motd,编辑这个文件就可以设置每次登录提示的内容。例如:

[root@moli_linux1 ~]# cat /etc/motd
welcome to my Linux Server!


打开一个新的shell
Connecting to 192.168.30.3:22...
Connection established.
To escape to local shell, press 'Ctrl+Alt+]'.

WARNING! The remote SSH server rejected X11 forwarding request.
Last login: Sat Jan 26 19:35:54 2019 from 192.168.30.1
welcome to my Linux Server!

可以看到登录提示符。

定义变量不加单引号和双引号,加单引号,加双引号的区别

[root@moli_linux1 ~] a=10
[root@moli_linux1 ~] b=10-$a
[root@moli_linux1 ~] c='100-$a'
[root@moli_linux1 ~] d="100-$a"
[root@moli_linux1 ~] echo $a
10
[root@moli_linux1 ~] echo $b
10-10
[root@moli_linux1 ~] echo $c
100-$a
[root@moli_linux1 ~] echo $d
100-10

第一种定义b变量的方式,当内容为简单连续的数字,字符串,路径名时,可以这样用,不加引号时,值里有变量的会被解析后输出。
第二种定义c的变量有加单引号,这样输出变量时单引号里面内容是什么就是什么,即使有变量和命令也是直接输出。
第三种定义d变量的方式加了双引号,这种输出变量内容时引号里的变量以及命令会经过解析后再输出内容,这种方式比较适合字符串中附带有变量以及命令且想将其解析后再输出的变量定义。

取消变量
取消变量使用unset命令,注意要取消的变量之前不用添加$符号。

[root@moli_linux1 ~] a=100
[root@moli_linux1 ~] echo $a
100
[root@moli_linux1 ~] unset a
[root@moli_linux1 ~] echo $a #可以看到这个变量的为空 

[root@moli_linux1 ~]#

将一个命令的结果赋值给一个新的变量
生产环境中把命令的结果作为变量的内容进行赋值的方法在脚本开发时很常见。有两个方法可以实现。

变量名=$(命令)
或者:
变量名=`命令`
[root@moli_linux1 ~] ls
192.168.229  anaconda-ks.cfg  git_data  server  shell-100  test-find

[root@moli_linux1 ~] file=$(ls)
[root@moli_linux1 ~] echo $file
192.168.229 anaconda-ks.cfg git_data server shell-100 test-find

[root@moli_linux1 ~] file2=`ls`
[root@moli_linux1 ~] echo $file2
192.168.229 anaconda-ks.cfg git_data server shell-100 test-find

Shell中的特殊位置变量

shell中特殊位置参数变量
要从命令行,函数或脚本执行等处传递参数时,就要在shell脚本中使用位置参数变量。

  • $0 :获取当前执行的shell脚本的文件名,如果执行脚本包含了路径,那么就包括脚本路径
  • $1:获得当前执行的shell脚本的第n个参数值,n=1.......9,当n为0时表示脚本的文件名,当n大于9时,则用大括号括起来,例如$(10),接的参数以空格隔开
  • $# : 获取当前执行的shell脚本后面接的参数的总个数
  • $* :获取当前Shell脚本所有传参的参数,不加引号和$@相同,如果给$*加上双引号,例如“$*”,则表示将所有的参数视为单个字符串,相当于“$1$2$3”
  • #@ :获取当前shell脚本所有传参的参数,不加引号和$*相同;如果给$@加上双引号,例如:“$@”,则表示将所有的参数视为不同独立的字符串,相当于“$1","$2"."$3”....这是将多参数传递给其他程序的最佳方式。

示例1,特殊位置变量$1

[root@moli_linux1 script] cat test.sh 
#!/bin/bash
echo $1 #脚本的功能是打印脚本传递的第一个参数的值
[root@moli_linux1 script] sh test.sh laowan #传入一个字符串参数,赋值给$1
laowan
[root@moli_linux1 script] sh test.sh laowan xiaoming #传入多个参数,但脚本不会接收多个参数,因此只输出第一个参数的值
laowan
[root@moli_linux1 script] sh test.sh "I am laowan" #用双引号括起来代表一个参数
I am laowan

示例2,特殊位置变量$1,$2,$3....${10}

[root@moli_linux1 script] echo \${1..15} > test2.sh
[root@moli_linux1 script] cat test2.sh 
#!/bin/bash
echo $1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15

[root@moli_linux1 script] sh test2.sh {a..z}  #传入26个字母a~z,作为26个参数
a b c d e f g h i a0 a1 a2 a3 a4 a5 #位置参数的数字大于9后,输出内容就不对了

[root@moli_linux1 script] vim test2.sh 

[root@moli_linux1 script] cat test2.sh 
#!/bin/bash
#位置参数的数字大于9时,需要用大括号将数字括起来
echo $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11} ${12} ${13} ${14} ${15}

[root@moli_linux1 script] sh test2.sh {a..z} 
a b c d e f g h i j k l m n o #大于9的数字加上大括号后显示正确的内容

示例3,特殊变量$0--获取脚本的名称以及路径:

[root@moli_linux1 shell_test] cat 05-getName.sh 
#!/bin/bash
echo $0
[root@moli_linux1 shell_test] sh 05-getName.sh 
05-getName.sh

示例4,$#获取脚本传递参数个数:

[root@moli_linux1 shell_test] cat 06-getNum.sh 
#!/bin/bash
echo $0 $1 $2 $3 $4 $5 $6 $7 $8 $9
echo $# #传递的参数个数
[root@moli_linux1 shell_test] ./06-getNum.sh {a..z} #传递了26个字母,即26个参数
./06-getNum.sh a b c d e f g h i #接受了9个参数,因此打印9个字母
26 #传递了26个参数,打印出26

示例5,$#的常见实际应用
根据用户在命令行传参的个数判断用户的输入,不符合的给出提示并退出,符合的打印出传入的参数内容。

[root@moli_linux1 shell_test] cat t1.sh 
#!/bin/bash
if [  $# -ne 2 ] #如果执行脚本传参的个数不等于2
    then
      echo "USAGE:/bin/sh $0 arg1 arg2" #则给用户提示正确的用法,此处$0,打印脚本名字和路径

      exit 1 #不满要求,则退出脚本,返回值为1
fi
    echo $1 $2 #符合要求,则打印$1 $2所获取到的传参的字符串

[root@moli_linux1 shell_test] sh t1.sh arg1 agr2  #符合传参个数=2,打印出传入参数的内容
arg1 agr2
[root@moli_linux1 shell_test] sh t1.sh arg1 agr2 arg3 #不符合传参个数=2,给出提示并退出
USAGE:/bin/sh t1.sh arg1 arg2

Shell中特殊状态变量

  • $? : 获取执行上一个指令的执行状态返回值(0为成功,非0为失败),这个变量最常用。
  • $$ : 获取当前执行的shell脚本的进程号(PID),不常用。
  • $! : 获取上一个在后台工作的进程号(PID),不常用。
  • $_ : 获取在此之前执行的命令或脚本的最后一个参数,不常用。

$?的实践1

[root@moli_linux1 ~] pwd  #执行pwd获取当前路径,用echo $?查看执行命令的状态返回值
/root
[root@moli_linux1 ~] echo $?  
0 #返回0表示上一条命令执行成功

[root@moli_linux1 ~] rm shell_test/ #删除目录shell_test
rm: 无法删除"shell_test/": 是一个目录 #没加参数-r ,无法删除

[root@moli_linux1 ~] echo $?
1 #状态码非0,表示上一条执行不成功或者错误

$?的实践2

[root@moli_linux1 ~] cat test1.sh 
#!/bin/bash
[  $# -ne 2 ] && {       #如果参数个数不=2
echo "must be two args." #则输出提示
exit 119                 #终止程序并以指定的119状态码退出程序,赋值给当前shell的“$?”变量。
}
echo "success!"

[root@moli_linux1 ~]# sh test1.sh 1 2 3 #参数个数不=2
must be two args.
[root@moli_linux1 ~]# echo $?
119 #状态码为119

"$?"的注意点:

  1. 判断命令,脚本或函数等程序是否执行成功。常用于源码编译安装软件,在每个步骤获 取“$?”的状态码来判断命令执行是否成功。
  2. 若是在脚本中调用执行"exit 数字",则会返回这个数字给"$?"变量。
  3. 如果是在函数里,通过"return 数字"把这个数字以函数返回值的形式传给"$?"。

Shell变量字串

示例1、变量子串实践

[root@moli_linux1 ~] moli="i am boy" #定义变量moli
[root@moli_linux1 ~] echo ${moli}    #带大括号打印变量moli
i am boy
[root@moli_linux1 ~] echo $moli      #直接打印变量moli
i am boy

示例2、返回变量值的长度的三种方法

[root@moli_linux1 ~]# echo ${#moli}  #返回moli变量值的长度
8     #“i am boy”                    #这些字符加起来刚好等于8(包括空格)
[root@moli_linux1 ~]# echo $moli |wc -L  #返回moli变量值的长度
8
[root@moli_linux1 ~]# echo "$moli" | awk '{print length($0)}'  #返回moli变量值的长度
8

示例3、截取moli变量的内容

[root@moli_linux1 ~]# echo ${moli}
i am boy
[root@moli_linux1 ~]# echo ${moli:2} #截取变量moli的内容,从第2个字符开始截取,即I后面空格后截取
am boy
[root@moli_linux1 ~]# echo ${moli:3}#从第3个字符开始截取,即从a后开始截取
m boy
[root@moli_linux1 ~]# echo ${moli:2:2}#从第2个字符开始截取,截取2个字符
am
[root@moli_linux1 ~]# echo ${moli:2:4}#从第2个字符开始截取,截取4个字符
am b

示例4、子串的匹配删除

[root@moli_linux1 ~] echo $moli
abcABC123ABCabc

[root@moli_linux1 ~] echo ${moli#a*c} #从开头删除匹配最短的a*c
ABC123ABCabc  #匹配到abcABC123ABCabc前三个字母并删除

[root@moli_linux1 ~] echo ${moli#a*C} #从开头删除匹配最短的a*C
123ABCabc      

[root@moli_linux1 ~] echo ${moli##a*c} #从开头删除匹配最长的a*c
                         

[root@moli_linux1 ~] echo ${moli##a*C} #从开头删除匹配最长的a*C
abc                 

[root@moli_linux1 ~] echo ${moli%a*C} #从结尾删除匹配最短的a*C
abcABC123ABCabc   #没有匹配到符合的字符串,保持原样

[root@moli_linux1 ~] echo ${moli%a*c} #从结尾删除匹配最短的a*c
abcABC123ABC       

[root@moli_linux1 ~] echo ${moli%%a*C} #从结尾删除匹配最长的a*C
abcABC123ABCabc   没有匹配到符合的字符串,保持原样

[root@moli_linux1 ~] echo ${moli%%a*c} #从结尾删除匹配最长的a*c
                         #匹配到全部字符串,删除全部
[root@moli_linux1 ~]#

总结:

  • #表示从开头删除匹配最短。
  • ##表示从开头删除匹配最长。
  • %表示从结尾删除匹配最短。
  • %%表示从结尾删除匹配最长。

示例5、子串的替换

[root@moli_linux1 ~] echo $moli
mooli is a boy.yes,a handsome boy
[root@moli_linux1 ~] echo ${moli/boy/girl} #替换匹配到的第一个字符串
mooli is a girl.yes,a handsome boy
[root@moli_linux1 ~] echo ${moli//boy/girl}#替换匹配到的所有字符串
mooli is a girl.yes,a handsome girl

总结:

  • 一个" / "表示替换匹配到的第一个字符串;
  • 两个" // "表示替换匹配到的所有字符串。

例子:去掉下列所有文件的文件名中的" _finished "字符串

[root@moli_linux1 变量子串案例] ls
stu_102999_1_finished.jpg  
stu_102999_2_finished.jpg 
stu_102999_3_finished.jpg
stu_102999_4_finished.jpg  
stu_102999_5_finished.jpg
[root@moli_linux1 变量子串案例] f="stu_102999_1_finished.jpg"
[root@moli_linux1 变量子串案例] echo $f
stu_102999_1_finished.jpg
[root@moli_linux1 变量子串案例] mv $f `echo ${f//_finished/}`  #单个文件的改名命令mv
[root@moli_linux1 变量子串案例] ls  #实现了单个文件的改名,把”_finished“替换成空
stu_102999_1.jpg           
stu_102999_2_finished.jpg  
stu_102999_3_finished.jpg
stu_102999_4_finished.jpg  
stu_102999_5_finished.jpg
# 接下来进行批量处理,利用循环。
[root@moli_linux1 变量子串案例]# for f in `ls *fin*.jpg`;do mv $f `echo ${f//_finished/}`;done
[root@moli_linux1 变量子串案例]# ls
stu_102999_1.jpg  
stu_102999_2.jpg  
stu_102999_3.jpg 
stu_102999_4.jpg  
stu_102999_5.jpg

相关推荐