Shell学习笔记

本文参考这个网址:http://c.biancheng.net/view/735.html
以及结合自己的学习来写的。本文只涉及一些简单的常用的shell脚本命令。

1.Shell认识

Shell 脚本的优势在于处理偏操作系统底层的业务,例如,Linux 内部的很多应用(有的是应用的一部分)都是使用 Shell 脚本开发的,因为有 1000 多个 Linux 系统命令为它作支撑,特别是 Linux 正则表达式以及三剑客 grep、awk、sed 等命令。
对于一些常见的系统脚本,使用 Shell 开发会更简单、更快速,例如,让软件一键自动化安装、优化,监控报警脚本,软件启动脚本,日志分析脚本等,虽然 Python 也能做到这些,但是考虑到掌握难度、开发效率、开发习惯等因素,它们可能就不如 Shell 脚本流行以及有优势了。对于一些常见的业务应用,使用 Shell 更符合 Linux 运维简单、易用、高效的三大原则。
Shell 既是一种脚本编程语言,也是一个连接内核和用户的软件。
常见的 Shell 有 sh、bash、csh、tcsh、ash 等。
sh 是 UNIX 上的标准 shell,很多 UNIX 版本都配有 sh。sh 是第一个流行的 Shell。
bash shell 是 Linux 的默认 shell,本教程也基于 bash 编写。bash 兼容 sh 意味着,针对 sh 编写的 Shell 代码可以不加修改地在 bash 中运行。
Shell 是一个程序,一般都是放在/bin或者/usr/bin目录下,当前 Linux 系统可用的 Shell 都记录在/etc/shells文件中。/etc/shells是一个纯文本文件,你可以在图形界面下打开它,也可以使用 cat 命令查看它。
通过 cat 命令来查看当前 Linux 系统的可用 Shell:

$ cat /etc/shells 
/bin/sh
/bin/bash
/sbin/nologin
/bin/dash
/bin/tcsh
/bin/csh
/bin/ksh
/usr/bin/ksh
/usr/bin/pdksh

在现代的 Linux 上,sh 已经被 bash 代替,/bin/sh往往是指向/bin/bash的符号链接。
如果你希望查看当前 Linux 的默认 Shell,那么可以输出 SHELL 环境变量:

$ echo $SHELL
/bin/bash

输出结果表明默认的 Shell 是 bash。

2.第一个Shell脚本

Shell脚本基本上都是以.sh为结尾。(也可以不是,只是约定俗成)
touch一个HelloWorld.sh文件,vi编辑一下:

#!/bin/bash
echo "Hello World !"

第 1 行的#!是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell;后面的/bin/bash就是指明了解释器的具体位置。
第 2 行的 echo 命令用于向标准输出文件(Standard Output,stdout,一般就是指显示器)输出文本。在.sh文件中使用命令与在终端直接输入命令的效果是一样的。

3.第二个shell脚本
      1 #!/bin/bash
      2 #zhusi
      3 
      4 echo "where are you from?"
      5 read COUNTRY
      6 echo "I like $COUNTRY"

第 5 行中表示从终端读取用户输入的数据,并赋值给 COUNTRY变量。read 命令用来从标准输入文件(Standard Input,stdin,一般就是指键盘)读取用户输入的数据。
6 行表示输出变量 COUNTRY的内容。注意在变量名前边要加上$,否则变量名会作为字符串的一部分处理。

4.执行shell脚本

一般要赋一下可执行的权限chmod +x
chmod +x ./test.sh赋过权限之后,可以直接用 ./test.sh 执行。

$  ./test.sh 
where are you from?
China
I like China

如果没有chmod +x,没有可执行权限,不可以直接执行,但是可以这样,直接运行 Shell 解释器,将脚本文件的名字作为解释器的参数:

$ /bin/bash test.sh   #使用Bash的绝对路径
where are you from?
America
I like America

假如使用sh、bash命令执行脚本文件,可以没有+x 和 第一行解释器#!/bin/bash。假如不是使用sh、bash命令,那么需要+x 且 脚本第一行要是:#!/bin/bash。

5.Shell脚本的debug模式 -x

debug模式可以在你写shell脚本的时候,调试用的,看看执行哪地方有问题,放在生产上的时候,要把debug模式去掉的。
有两种方式:

wc.sh文件:

#!/bin/bash
echo "www.ruozedata.com"
ls
echo 1234

sh +x wc.sh 执行文件

#sh -x wc.sh 
+ echo www.ruozedata.com
www.ruozedata.com
+ ls
wc.sh
+ echo 1234
1234


wc.sh文件(第一行加了-x):

#!/bin/bash -x
echo "www.ruozedata.com"
ls
echo 1234

直接执行:

#./wc.sh 
+ echo www.ruozedata.com
www.ruozedata.com
+ ls
wc.sh
+ echo 1234
1234
$变量就可以获取当前进程的 PID

Linux 中的每一个进程都有一个唯一的 ID,称为 PID,使用$$变量就可以获取当前进程的 PID。

#!/bin/bash
echo $$  #输出当前进程PID
$ ./check.sh
bash: ./check.sh: 权限不够
$ chmod +x check.sh 
$ ./check.sh 
1839      #Shell脚本所在进程PID
$ echo $$
1392      #当前进程PID
6.Shell变量

在shell脚本里,养成好习惯:变量都用大写来表示。而且同一个名字,大写和小写代表两个不同的变量。
在 Bash shell 中,每一个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储。这意味着,Bash shell 在默认情况下不会区分变量类型,即使你将整数和小数赋值给变量,它们也会被视为字符串,这一点和大部分的编程语言不同。
Shell 支持以下三种定义变量的方式:

variable=value
variable='value'
variable="value"

variable 是变量名,value 是赋给变量的值。如果 value 不包含任何空白符(例如空格、Tab 缩进等),那么可以不使用引号;如果 value 包含了空白符,那么就必须使用引号包围起来。使用单引号和使用双引号也是有区别的。
注意,赋值号=的周围不能有空格
变量名外面的花括号{ }是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:

skill="Java"
echo "I am good at ${skill}Script"

如果不给 skill 变量加花括号,写成echo “I am good at $skillScript”,解释器就会把 $skillScript 当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。
推荐给所有变量加上花括号{ },这是个良好的编程习惯。

单引号和双引号的区别

前面我们还留下一个疑问,定义变量时,变量的值可以由单引号’ '包围,也可以由双引号" "包围,它们到底有什么区别呢?不妨以下面的代码为例来说明:

#!/bin/bash
url="http://c.biancheng.net"
website1='C语言中文网:${url}'
website2="C语言中文网:${url}"
echo $website1
echo $website2
运行结果:
C语言中文网:${url}
C语言中文网:http://c.biancheng.net

以单引号’ '包围变量的值时,单引号里面是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。这种方式比较适合定义显示纯字符串的情况,即不希望解析变量、命令等的场景。
以双引号" "包围变量的值时,输出时会先解析里面的变量和命令,而不是把双引号中的变量名和命令原样输出。这种方式比较适合字符串中附带有变量和命令并且想将其解析后再输出的变量定义。
建议:如果变量的内容是数字,那么可以不加引号;如果真的需要原样输出就加单引号;其他没有特别要求的字符串等最好都加上双引号,定义变量时加双引号是最常见的使用场景。

将命令的结果赋值给变量
shell 也支持将命令的执行结果赋值给变量,常见的有以下两种方式:

variable=`command`
variable=$(command)

第一种方式把命令用反引号` `(位于 Esc 键的下方)包围起来,反引号和单引号非常相似,容易产生混淆,所以不推荐使用这种方式;第二种方式把命令用$()包围起来,区分更加明显,所以推荐使用这种方式。

bash-4.1$ cat test1.sh 
skill="Java"
echo "I am good at ${skill}Script"
bash-4.1$ test='cat test1.sh'
bash-4.1$ echo $test
cat test1.sh
bash-4.1$ test=$(cat test1.sh)
bash-4.1$ echo $test
skill="Java" echo "I am good at ${skill}Script"
bash-4.1$ test=`cat test1.sh`
bash-4.1$ echo $test
skill="Java" echo "I am good at ${skill}Script"
bash-4.1$ 

获取字符串的长度:

$ str="test"
$ echo $str
test
$ echo ${#str}
4
7.Shell数组

Shell 数组元素的下标也是从 0 开始计数。获取数组中的元素要使用下标[ ],Bash Shell 只支持一维数组,不支持多维数组。
在 Shell 中,用括号( )来表示数组,数组元素之间用空格来分隔。由此,定义数组的一般形式为:

array_name=(ele1  ele2  ele3 ... elen)

注意,赋值号=两边不能有空格,必须紧挨着数组名和数组元素。
Shell 是弱类型的,它并不要求所有数组元素的类型必须相同。

arr=(20 56 "http://c.biancheng.net/shell/")

第三个元素就是一个“异类”,前面两个元素都是整数,而第三个元素是字符串。
获取数组元素的值,一般使用下面的格式:

${array_name[index]}

其中,array_name 是数组名,index 是下标。例如:

n=${nums[2]}

表示获取 nums 数组的第二个元素,然后赋值给变量 n。再如:

echo ${nums[3]}

表示输出 nums 数组的第 3 个元素。
使用@或*可以获取数组中的所有元素,例如:

${nums[*]}
${nums[@]}

两者都可以得到 nums 数组的所有元素。

Shell获取数组长度,格式如下:

${#array_name[@]}
${#array_name[*]}

其中 array_name 表示数组名。两种形式是等价的,选择其一即可。
获取字符串长度:

${#string_name}

string_name 是字符串名。

将命令的输出结果作为值赋给某个变量,Shell 中有两种方式可以完成,一种是反引号` `,一种是$(),使用方法如下:

var_name=`command`
var_name=$(command)

其中,var_name 是变量名,command 是要输出的命令。
运行系统命令 date 可以得到当前的系统时间。在很多时候我们需要记录脚本运行时间,所以只是运行这个命令是没有意义的,必须将该命令的运行结果记录并保存到变量中,并持久化到文件中,才能为后期分析提供有用的参考依据。

#!/bin/bash

DATE_01=`date`
DATE_02=$(date)
echo $DATE_01
echo $DATE_02

运行结果:
2019年 04月 12日 星期五 14:39:36 CST
2019年 04月 12日 星期五 14:39:36 CST
如果被替换的命令的输出内容包括多行(也即有换行符),或者含有多个连续的空白符,那么在输出变量时应该将变量用双引号包围,否则系统会使用默认的空白符来填充,这会导致换行无效,以及连续的空白符被压缩成一个。所以,为了防止出现格式混乱的情况,建议在输出变量时加上双引号。

Shell (()):对整数进行数学运算

双小括号 (( )) 是 Bash Shell 中专门用来进行整数运算的命令,它的效率很高,写法灵活,是企业运维中常用的运算命令。注意:(( )) 只能进行整数运算,不能对小数(浮点数)或者字符串进行运算。 bc 命令可以用于小数运算。
((表达式 )) 表达式可以只有一个,也可以有多个,多个表达式之间以逗号,分隔。对于多个表达式的情况,以最后一个表达式的值作为整个 (( )) 命令的执行结果。
可以使用$获取 (( )) 命令的结果,这和使用$获得变量值是类似的。

8.if else语句

touch一个文件 if.sh,代码为:

#!/bin/bash

A="abc"
B="jerry"

if [ ${a} == ${b} ]; then
        echo "=="
else
        echo "!="
fi

执行:

[root@10-9-140-90 shell]# ./if.sh 
==
[root@10-9-140-90 shell]# sh -x if.sh    #debug模式
+ A=abc
+ B=jerry
+ '[' == ']'
+ echo ==
==

为什么上面代码显示的结果为 == ,因为A和a不是一个变量,系统找不到a变量,所以a变量为空值,b也是一样,所以最后两个空值就是相等了。改成大写之后就对了。
需要注意的是:if [ ${a} == ${b} ] 中间全是有空格的。
使用 if 语句,它的语法格式为:

if  condition
then
    statement(s)
fi

如果你喜欢,也可以将 then 和 if 写在一行:

if  condition;  then
    statement(s)
fi

请注意 condition 后边的分号;,当 if 和 then 位于同一行的时候,这个分号是必须的,否则会有语法错误。
下面的例子使用 if 语句来比较两个数字的大小:

#!/bin/bash
read a
read b
if (( $a == $b ))
then
    echo "a和b相等"
fi

在判断条件中也可以使用逻辑运算符,例如:

#!/bin/bash
read age
read iq
if (( $age > 18 && $iq < 60 ))
then
    echo "你都成年了,智商怎么还不及格!"
    echo "来C语言中文网(http://c.biancheng.net/)学习编程吧,能迅速提高你的智商。"
fi

即使 then 后边有多条语句,也不需要用{ }包围起来,因为有 fi 收尾。
输入一个整数,输出该整数对应的星期几的英文表示:

#!/bin/bash
printf "Input integer number: "
read num
if ((num==1)); then
    echo "Monday"
elif ((num==2)); then
    echo "Tuesday"
elif ((num==3)); then
    echo "Wednesday"
elif ((num==4)); then
    echo "Thursday"
elif ((num==5)); then
    echo "Friday"
elif ((num==6)); then
    echo "Saturday"
elif ((num==7)); then
    echo "Sunday"
else
    echo "error"
fi
9.shell 循环语句

主要有while循环、until 循环和for循环。
while循环
while 循环是 Shell 脚本中最简单的一种循环,当条件满足时,while 重复地执行一组语句,当条件不满足时,就退 while 循环。

【实例1】计算从 1 加到 100 的和。

#!/bin/bash
i=1
sum=0
while ((i <= 100))
do
    ((sum += i))
    ((i++))
done
echo "The sum is: $sum"

运行结果:
The sum is: 5050
【实例2】实现一个简单的加法计算器,用户每行输入一个数字,计算所有数字的和。

#!/bin/bash
sum=0
echo "请输入您要计算的数字,按 Ctrl+D 组合键结束读取"
while read n
do
    ((sum += n))
done
echo "The sum is: $sum"

运行结果:
12↙
33↙
454↙
6767↙
1↙
2↙
The sum is: 7269
在终端中读取数据,可以等价为在文件中读取数据,按下 Ctrl+D 组合键表示读取到文件流的末尾,此时 read 就会读取失败,得到一个非 0 值的退出状态,从而导致判断条件不成立,结束循环。
for循环
Shell for 循环有两种使用形式。

for((exp1; exp2; exp3))
do
    statements
done

几点说明:
exp1、exp2、exp3 是三个表达式,其中 exp2 是判断条件,for 循环根据 exp2 的结果来决定是否继续下一次循环;
statements 是循环体语句,可以有一条,也可以有多条;
do 和 done 是 Shell 中的关键字。

#!/bin/bash
sum=0
for ((i=1; i<=100; i++))
do
    ((sum += i))
done
echo "The sum is: $sum"

运行结果:
The sum is: 5050

for variable in value_list
do
    statements
done

variable 表示变量,value_list 表示取值列表,in 是 Shell 中的关键字。
每次循环都会从 value_list 中取出一个值赋给变量 variable,然后进入循环体(do 和 done 之间的部分),执行循环体中的 statements。直到取完 value_list 中的所有值,循环就结束了。

#!/bin/bash
sum=0
for n in 1 2 3 4 5 6
do
    echo $n
     ((sum+=n))
done
echo "The sum is "$sum
#!/bin/bash
for str in "C语言中文网" "http://c.biancheng.net/" "成立7年了" "日IP数万"
do
    echo $str
done

运行结果:

C语言中文网
http://c.biancheng.net/
成立7年了
日IP数万

给出一个取值范围:{start…end}
start 表示起始值,end 表示终止值;注意中间用两个点号相连,而不是三个点号。根据笔者的实测,这种形式只支持数字和字母。
例如,计算从 1 加到 100 的和:

#!/bin/bash
sum=0
for n in {1..100}
do
    ((sum+=n))
done
echo $sum

计算从 1 到 100 之间所有偶数的和:

#!/bin/bash
sum=0
for n in $(seq 2 2 100)
do
    ((sum+=n))
done
echo $sum

运行结果:
2550
seq 是一个 Linux 命令,用来产生某个范围内的整数,并且可以设置步长,不了解的读者请自行百度。seq 2 2 100表示从 2 开始,每次增加 2,到 100 结束。
列出当前目录下的所有 Shell 脚本文件:

#!/bin/bash
for filename in $(ls *.sh)
do
    echo $filename
done

运行结果:
demo.sh
test.sh
abc.sh
ls 是一个 Linux 命令,用来列出当前目录下的所有文件,*.sh表示匹配后缀为.sh的文件,也就是 Shell 脚本文件。

10.分割

touch一个脚本spilt.sh

#!/bin/bash
S="ruoze,jepson,xingxing,dashu,xiaoshiqi,xiaohai"
OLD_IFS="$IFS"
IFS=","
arr=($S)
IFS="OLD_IFS"

for x in ${arr[*]}
do
        echo $x
done

执行结果:

[root@10-9-140-90 shell]# ./spilt.sh 
ruoze
jepson
xingxing
dashu
xiaoshiqi
xiaohai

OLD_IFS="$IFS"
IFS=","
arr=($S)
IFS=“OLD_IFS”
这几个都是固定的写法,在大数据里面经常用,记住就行了。

11.awk命令(面试题笔试)

touch一个awk.log文件:

a b c d
1 2 3 4
5 6 7 8
[root@10-9-140-90 shell]# cat awk.log |awk '{print $1}'  #打印第一列
a
1
5
[root@10-9-140-90 shell]# cat awk.log |awk '{print $5}' #打印一个不存在的列



[root@10-9-140-90 shell]# cat awk.log |awk '{print $0}'  #打印所有
a b c d
1 2 3 4
5 6 7 8
[root@10-9-140-90 shell]# cat awk.log |awk '{print $1,$2}'  #面试 打印第一列第二列,中间有逗号
a b
1 2
5 6
[root@10-9-140-90 shell]# cat awk.log |awk '{print $1$2}'   #面试 打印第一列第二列,中间没有逗号
ab
12
56
[root@10-9-140-90 shell]# cat awk.log |awk 'NR==1'   #打印第一行
a b c d
[root@10-9-140-90 shell]# cat awk.log |awk 'NR==5'  #打印不存在的行
[root@10-9-140-90 shell]# cat awk.log |awk 'NR>1&&NR<4'  #打印2到3行
1 2 3 4
5 6 7 8

现在把awk.log文件修改成:逗号分隔(大数据里,可能是其他分隔符)

a,b,c,d
1,2,3,4
5,6,7,8
[root@10-9-140-90 shell]# cat awk.log |awk '{ print $1,$2 }'   #再用之前的方式发现不能打印前两列了,打印了所有的
a,b,c,d 
1,2,3,4 
5,6,7,8
[root@10-9-140-90 shell]# cat awk.log |awk -F "," '{ print $1,$2 }'   #这个时候需要加个-F参数就可以打印前两列了
a b
1 2
5 6
[root@10-9-140-90 shell]# awk -F "," '{ print $1,$2 }' awk.log  #也可以这样直接读取文件(和上面一样,如果文件很大的话就不用cat,但是一般shell脚本里操作的日志文件不是很大)
a b
1 2
5 6
[root@10-9-140-90 shell]# awk -F "," 'NR>1{ print $3,$4 }' awk.log   #取第二行第三行的第三列第四列
3 4
7 8

其它的可以网上查一下或者查看命令帮助:awk --help

12.sed替换命令

touch一个sed.log文件:

a b c d
1 2 3 4 
5 6 7 8 
9 10 11 12
[root@10-9-140-90 shell]# cat sed.log 
a b c d
1 2 3 4
5 6 7 8
9 10 11 12
[root@10-9-140-90 shell]# sed -i 's/a/aa/' sed.log   #把文件里面的a替换成aa
[root@10-9-140-90 shell]# cat sed.log 
aa b c d
1 2 3 4
5 6 7 8
9 10 11 12
[root@10-9-140-90 shell]# sed -i "s/aa/aa'/" sed.log   #用双引号
[root@10-9-140-90 shell]# cat sed.log 
aa' b c d
1 2 3 4
5 6 7 8
9 10 11 12
[root@10-9-140-90 shell]# sed -i "s?aa?bb'?" sed.log  #  /换成?也可以
[root@10-9-140-90 shell]# cat sed.log 
bb'' b c d
1 2 3 4
5 6 7 8
9 10 11 12
[root@10-9-140-90 shell]# sed -i "s/b/w/" sed.log  #想把文件里的b全部替换成w,结果发现不对,只替换了每一行的第一个b,后面的b都没有替换
[root@10-9-140-90 shell]# cat sed.log 
wb'' b c d
1 2 3 4
5 6 7 8
9 10 11 12
[root@10-9-140-90 shell]# sed -i "s/b/w/g" sed.log  #后面要个g,就全局替换了,把所有的b都替换了w
[root@10-9-140-90 shell]# cat sed.log 
ww'' w c d
1 2 3 4
5 6 7 8
9 10 11 12
[root@10-9-140-90 shell]# sed -i "s/^/uuu&/g" sed.log  #每行前面都加个uuu:"s/^/uuu&/g"
[root@10-9-140-90 shell]# cat sed.log 
uuuww'' w c d
uuu1 2 3 4
uuu5 6 7 8
uuu9 10 11 12
[root@10-9-140-90 shell]# sed -i "s/$/&uuu/g" sed.log  #每行行尾都加个uuu:"s/$/&uuu/g"
[root@10-9-140-90 shell]# cat sed.log 
uuuww'' w c duuu
uuu1 2 3 4uuu
uuu5 6 7 8uuu
uuu9 10 11 12uuu

公司用的最多的是全局替换:

sed -i "s/b/w/g" sed.log

如何实现:
shell脚本 发送QQ邮件+附件log文件?
shell脚本 发送一个html表格 到QQ邮箱?

你可能感兴趣的:(Shell学习笔记)