一、  BashShell脚本初步

   之前我们学习了很多Linux命令,但是这些命令都是在命令提示符下执行的,一次只能执行一条命令并产生结果。如创建一个文件或目录等等。但是有些情况下,我们需要执行多个命令来完成一个完整的任务,我们可以在命令行提示符下也可以同时执行多个命令,但需要用分号分割每个单独的命令,如下所示,先定位到当前用户的工作目录下,然后在目录下创建一个logs目录,并且在logs目录中创建一个空的syslog.log日志文件并在日志文件中添加当前的系统日期和时间。

   命令:

cd  /home/yarn ; mkdir logs ; cd logs ; touch syslog.log ; date > logs.log

 

   命令执行后,会在/home/yarn目录下首先创建一个目录logs,然后切换到logs目录下,通过touch命令创建一个空的日志文件syslog.log,然后使用date命令将当前系统日期和时间添加到syslog.log日志文件中。

   syslog.log日志内容:

2016 02  18 星期四  17:49:47 CST

 

   虽然我们在命令行提示符同时执行了多条命令,也完成了我们想要的任务,但是这样的命令集并没有被保存,不能重复使用,另外,如果任务较复杂,这样的方式就不是更好的方式了。

 

9.1           创建一个Bash Shell脚本

   创建一个Bash脚本需要使用文件编辑器,将命令集输入到文本文件中,如下:

   命令:

vi  bashTest.sh

 

   bashTest.sh

#!/bin/bash

 

   在脚本的第一行需要输入#!/bin/bash,作用是指定Shell脚本解释器,如果不显示的指定,也可以执行,但是bash提供的函数不能使用。另外的情况是,用户默认的Shell解释器是bash,但是如果其他用户要执行此脚本,这个用户的默认的Shell解释器不是bash,脚本执行过程中可能会出现异常,所以,要求在脚本的第一行要显示的指定Shell脚本解释器。

   Shell脚本中,#号是用来注释信息的,解释器不会执行#号后面的命令,只把他当中一般的注释信息。注释可以用来说明脚本的使用场景和脚本的使用说明,方便其他用户使用脚本。

   下面创建一个简单的脚本,实现的功能和之前的例子相同,创建目录和日志文件。首先要使用文本编辑器创建一个文本文件用来编写脚本命令。

   命令:

vi  bash01.sh

 

例:bash01.sh

#!/bin/bash

# 创建的第一个bash脚本

# 此脚本的作用是在用户工作目录下创建一个目录,在目录中创建一个日志文件

# 并且将当前的系统日期和时间输入到文件中

 

# 切换到工作目录

cd  /home/yarn

# 创建logs目录

mkdir  logs

# 切换到logs目录

cd logs

# 创建一个空的日志文件

touch  syslog.log

# 将当前系统日期时间添加到syslog.log日志文件中

date  > syslog.log

 

   当第一次执行脚本时,系统会警告命令找不到,通常解决的办法有两种,第一种方法是将脚本所在的目录添加到环境变量PATH中,这样系统在执行脚本时会到PATH所指定的目录下去找,找到后执行。另一种方式是使用绝对路径或在脚本所在目录下使用./的方式执行脚本,如下:

/home/yarn/bash02/bash01.sh

cd  /home/yarn/bash02

./bash01.sh

 

   如果在环境变量PATH中指定路径或使用绝对路径执行脚本,系统还会发出警告,说用户没有执行的权限,这是因为创建脚本文件时默认情况下是没有执行权限的,如下:

-rw-rw-r--.  1 yarn yarn  440 2  19  11:51 bash01.sh

 

   这和系统默认设置有关,可以重新设置,后面会做介绍。

   看到脚本文件的属主有读写权限而没有执行权限,所以要通过命令chmod为属主设置可执行权限。

   命令:

chmod  u+x bash01.sh

 

   bash01.sh

-rwxrw-r--.  1 yarn yarn  440 2  19  11:52 bash01.sh

 

   好了,现在就可以执行脚本了。

 

9.1.1       echo命令

   echo命令是将字符串输出到标准输出,通常使用echo命令输出提示信息或检测结果是否正确。

   命令:

echo  Test Bash script!

 

   命令执行后会在控制台输出:Test Bash script!。没问题!因为echo命令默认后面跟着的是字符串,但是有些情况下,输出会出现不正确的结果,如下面的情况:

命令:

echo  Test 'Bash script'!

 

   控制台输出:

Test  Bash script!

 

   这不是我们想要的结果,单引号被过滤掉了,所以在使用echo命令时,建议将输出的字符串加上双引号,如下:

命令:

echo  "Test 'Bash script'"!

 


控制台输出:

Test  'Bash script'!

 

   这次的输出是我们想要的结果。

   另外,echo命令输出后会自动添加一个换行符,下一次输出将在新行输出,如下:

命令:

echo  "Test 'Bash script'"! ; echo "Helle bash"

 


控制台输出:

Test  'Bash script'!

Helle  bash

 

   有时候我们需要将输出同一行显示,这就需要使用-n参数,如下:

命令:

echo -n  "Test 'Bash script'"! ; echo "Helle bash"

 


控制台输出:

Test  'Bash script'!Helle bash

 

9.2           Bash脚本中使用变量

   Java或其他语言相似,在Bash脚本中也可以使用变量,将值保存到变量中在脚本中使用,下面通过例子说明在Bash脚本中变量的使用。在之前我们讲了Linux系统中的环境变量,那么对Bash脚本的变量就更容易理解了。

9.2.1       Bash脚本中的环境变量

   Linux系统中维护着一组环境变量,用来记录系统的各类信息,如当前的用户、用户ID和主机名称等等信息,这些环境变量的值可以在Bash脚本中使用。可以通过set命令看到Linux系统维护的环境变量的值。

   命令:

set

 

控制台输出:

BASH=/bin/bash

HADOOP_HOME=/usr/local/hadoop

HISTCONTROL=ignoredups

HISTFILE=/home/yarn/.bash_history

HOME=/home/yarn

HOSTNAME=YARN

HOSTTYPE=i386

IFS=$'  \t\n'

JAVA_HOME=/usr/local/jdk1.8.0_51

JRE_HOME=/usr/local/jdk1.8.0_51/jre

LANG=zh_CN.utf8

LOGNAME=yarn

OLDPWD=/home/yarn

PATH=/usr/local/jdk1.8.0_51/bin:/usr/local/jdk1.8.0_51/jre/bin:/usr/lib/qt-3.3/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/yarn/bash01:/usr/local/hadoop/sbin:/usr/local/hadoop/bin:/home/yarn/bin:/home/yarn/bash01

PIPESTATUS=([0]="0")

PPID=3054

PWD=/home/yarn/bash02

UID=500

USER=yarn

USERNAME=yarn

 

   这些环境变量是系统维护的,我们可以在脚本中使用,引用变量需要使用$符号,如下:

例:在脚本中访问系统环境变量

#!/bin/bash

# 使用系统维护的环境变量

 

echo "当前的用户:$USER"

echo "当前用户ID$UID"

echo "用户的工作目录:$HOME"

echo "主机名:$HOSTNAME"

echo "系统默认脚本解释器:$BASH"

 


控制台输出:

当前的用户:yarn

当前用户ID500

用户的工作目录:/home/yarn

主机名:YARN

系统默认脚本解释器:/bin/bash

 

   这里需要注意的是$符,系统会认为后面跟着的是一般变量,在脚本执行过程中,系统会将变量的值替换显示。所以如果想显示$符到控制台,需要进行转义:

echo "\$USER$USER"

$USERyarn

 

   对变量的引用可以可以采用${varname}的方式,大括号中是变量名称,修改上面的例子。

例:采用${varname}的方式访问系统环境变量

#!/bin/bash

# 使用系统维护的环境变量

 

echo "当前的用户:${USER}"

echo "当前用户ID${UID}"

echo "用户的工作目录:${HOME}"

echo "主机名:${HOSTNAME}"

echo "系统默认脚本解释器:${BASH}"

 


控制台输出:

当前的用户:yarn

当前用户ID500

用户的工作目录:/home/yarn

主机名:YARN

系统默认脚本解释器:/bin/bash

 

   使用效果是相同的,建议使用第二种方式。

 

9.2.2       Bash脚本中的用户变量

   在用户脚本中可以定义和使用变量,定义后的变量可以在脚本中引用。这些变量的生命周期同脚本的执行周期相同,脚本执行结束变量同时在内存中被销毁。为了区别环境变量,用户变量建议使用小写字母,并且变量Varvar是不同的两个变量,要注意变量名区分大小写。

   为变量赋值要使用等号,与其他语言的区别是,变量名、等号和变量值之间是不能存在空格。如下:

   例:

#!/bin/bash

name=张三

age=22

address=山西太原

 

echo "name$name"

echo "age$age"

echo "address$address"

 

   Bash脚本中的变量类型会根据变量的值决定,引用变量需要用到$符。

 

   例子:编写两个脚本文件,在第一个脚本中调用第二个脚本,父脚本中设置局部变量,在子脚本中进行访问,因为执行子脚本会启动一个新的子进程,所以父脚本中的局部变量不能访问到。

#!/bin/bash

#这是一个测试局部变量的脚本

name=张三

id=1001

echo  "name:$name"

echo  "id:$id"

echo  "bash02.sh进程号:$$"

#./bash02_1.sh

#bash  bash02_1.sh

#.  ./bash02_1.sh

source  ./bash02_1.sh

 

例子:编写两个脚本文件,在第一个脚本中调用第二个脚本,父脚本中设置环境变量并使用export命令将用户环境变量导出,在子脚本中进行访问父脚本导出的环境变量,因为执行子脚本会启动一个新的子进程,在子进程中可以访问到父脚本中的导出的用户环境变量。

#!/bin/bash

#测试用户环境变量

export  name=zhangsan

id=1001

export  id

echo  "name:$name"

echo  "id:$id"

echo  "bash03.sh父进程号:$PPID"

echo  "bash03.sh进程号:$$"

bash  /home/yarn/b2/bash03_1.sh

echo  "name:$name"

echo  "id:$id"

 

例子:编写两个脚本文件,在第一个脚本中使用”.”的方式调用第二个脚本,父脚本中设置局部变量,再设置用户环境变量并使用export命令将用户环境变量导出,在子脚本中进行访问父脚本导出的环境变量和局部变量,因为采用.的方式启动子脚本不会启动一个新的子进程,还是在父进程中运行,所以,在子进程中可以访问到父脚本中的导出的用户环境变量和局部变量。

#!/bin/bash

#这是一个测试局部变量的脚本

name=张三

id=1001

echo  "name:$name"

echo  "id:$id"

echo  "bash02.sh进程号:$$"

#./bash02_1.sh

#bash  bash02_1.sh

#.  ./bash02_1.sh

source  ./bash02_1.sh

 

9.3           exec命令

   当使用exec命令执行一个命令或启动一个脚本文件时,会启动一个子进程,但进程ID号还会使用父进程的进程ID号,对于父进程的局部变量exec命令启动的脚本进程是不可见的,而父进程导出的环境变量还可以引用到。当exec命令启动的脚本进程执行结束后,不会返回到父进程,而是将父进程终止。也可以理解为exec启动的脚本进程替代了当前的父进程,并继承了父进程的环境变量,当脚本执行结束后启动脚本进程的当前环境都将会被清除。

   语法格式为:

exec 命令

 

通常情况下命令是一个脚本文件。当这个脚本结束时,相应的进程就结束了。

例子:通常情况下,一个命令或脚本进程结束后,会回到启动命令或脚本进程的父进程,下面例子说明。

#!/bin/bash

#测试exec命令启动脚本

user=zhangsan

export country="中国"

echo "bash04.sh进程号:$$"

exec ./bash04_1.sh

echo "是否执行到这里"

 

例子:通过exec命令启动的命令或脚本进程执行结束后,不会回到启动脚本的父进程,而是会终止启动脚本的父进程,因为使用的进程ID还是父进程的进程ID,感觉上是没有启动新的进程,还在父进程中执行脚本,但是,实际上还是启动了新的子进程(对父进程的环境复制),只是进程ID(PID)还使用父进程的,当脚本执行结束,父进程也不存在了,不存在返回父进程。


 

9.3.1    exec命令执行Java程序

   使用exec命令执行Java程序,命令格式如下:

exec  java classname

exec  java -jar jarname

 

   使用exec命令执行Java程序,当程序执行结束后,执行exec命令的父进程也同样会结束。

   例子:


 

9.3.2    exec命令指定文件描述符

   exec命令指定文件描述符在后面的内容中会详细说明。

   例子:重定向输出

#!/bin/bash

#测试exec命令重定向输出

 

 

 

echo  "chongdingxiangqian"

exec  > log.txt

echo  "Hello zhangsan"

cat  bash03.sh

ls

 

9.4           脚本中的反引号

   反引号用于执行命令并且有输出的情况,如:ls,显示目录下的文件信息。并且将输出赋给一个变量。需要注意的是系统会将反引号中的内容作为一个命令并执行。

反引号可以将命令的标准输出值赋值给一个变量,如下:

#!/bin/bash

var=`ls  -a`

echo "var$var"

 

   控制台输出:

var: .

..

.abrt

bash01

.bash_history

.bash_logout

.bash_profile

.bashrc

.cache

.config

.dbus

.dmrc

.esd_auth

 

   下面通过一个例子说明反引号的使用方式。如系统服务每天要创建一个日志文件,每个日志文件名要带有日期。

   例:使用反引号返回日期字符串

#!/bin/bash

# 将日期格式化输出结果赋值给变量

datestr=`date +%y%m%d`

# 切换到保存日志的目录

cd /home/yarn/bash01

# 将日志的名称保存到一个变量中

logfile=log_${USER}_${datestr}.log

# 创建一个空的日志文件

touch $logfile

# 将当天的日期输入到文件的第一行

date > $logfile

 

   生成的文件:

log_yarn_160226.log

 

   log160226.log日志内容:

2016 02  26 星期五  17:29:43 CST

  

9.5           获取命令输出结果的另一种方式

   像反引号一样,还可以使用另一种方式获取命令的输出结果。命令格式如下:

var=$(命令)

 

   例子:可以用下面的方法读取命令序列的输出,与返引号相似。

filelist=$(ls  | cat -n)

echo $filelist

# 文件列表加行号

filelist=`ls  | cat -n`

echo $filelist

# 计算两个整数的和并返回结果

result1=`expr 3 + 4`

result2=$(expr 5 + 5)

echo $result1

echo $result2

 

# 创建临时文件,并返回文件名称

tmpfile1=`mktemp`

tmpfile2=$(mktemp)

echo $tmpfile1

echo $tmpfile2

 

# 创建临时目录并返回目录名称

tmpdir1=`mktemp -d`

tmpdir2=$(mktemp -d)

echo $tmpdir1

echo $tmpdir2

 

9.6           重定向输入和输出

   重定向是根据不同需求将标准输出到控制台的信息输出到其他的媒介,如将输出到控制台的警告信息重定向输出到日志文件便于查询。或将从标准输入设备键盘读取数据重定向成从文件中读取数据。

9.6.1       输出重定向

   输出重定向常用的方式是将输出到控制台的信息重定向输出到文件中。输出重定向符是大于号 >,命令格式如下:

命令 > filename

  

   只要是有输出信息的命令都可以使用输出重定向符。

ls -a  > filename

date  > filename

echo "login  USER$USER" > filename

 

   输出重定向符是大于号,需要注意的是,如果重定向输出的文件不存在,系统会首先创建文件然后将信息输入到文件中,如果文件存在,则直接将信息输入到文件中。通过输出重定向符每次输出的信息都会将文件中已经存在的信息覆盖,所以如果想向文件中追加信息而不覆盖已存在的信息,需要使用重定向符(>>),下面通过例子说明。

  

   例:将脚本执行过程中的信息重定向输出到日志文件。

#!/bin/bash

# 输出重定向

# 日期格式化到变量

datestr=`date  +%y%m%d`

# 日志文件命令

filename=/home/yarn/bash01/log_${USER}_${datestr}.log

# 将当天日期重定向输出到日志文件

date  > $filename

# 脚本执行信息追加到日志文件

echo "bash  script begin!" >> $filename

# 切换到工作目录

cd  /home/yarn/bash01

# 将信息追加到日志文件

echo  "select /home/yarn/bash01" >> $filename

# 创建工作目录

mkdir  mapreduce01

# 将信息追加到日志文件

echo  "create log dir:/home/yarn/bash01/mapreduce01" >> $filename

# 切换工作目录

cd  mapreduce01

# 将信息追加到日志文件

echo  "select /home/yarn/bash01/mapreduce01" >> $filename

# 拷贝文件到当前目录

cp  /home/yarn/bash01/bash04.sh .

# 将信息追加到日志文件

echo  "copy /home/yarn/bash01/bash04.sh to /home/yarn/bash01/mapreduce01"  >> $filename

# 将脚本执行结束信息追加到日志文件

echo  "bash script end!" >> $filename

 

   可以通过:>输出定向符清除文件内容,命令格式如下:

:> filename

 

   :>会把文件filename截断为0长度,就是清除文件内容。如果文件不存在, 那么就创建一个空的文件。:>是一个占位符和定向符组成。

   例:将输出信息重定向到文件后,将文件信息清除。

#!/bin/bash

# :>

# 定义变量,文件hello.txt不存在

FILE_DIR=/home/yarn/bash01/dir1/hello.txt

# 将标准输出到文件中

# "Hello Hadoop!.Hello Bash!"写到/home/yarn/bash01/dir1/hello.txt

# 不会在控制台输出,输出重定向到文件中了

echo  "Hello Hadoop!.Hello Bash!" > "$FILE_DIR"

# 显示文件/home/yarn/bash01/dir1/hello.txt内容:"Hello Hadoop!.Hello Bash!"

cat  "$FILE_DIR"

# 清空/home/yarn/bash01/dir1/hello.txt文件的内容

:>"$FILE_DIR"

# 显示文件内容,为空

cat  "$FILE_DIR"

 

exit 0

 

   例:将输出信息重定向到文件,比较>>>

#!/bin/bash

# >  >>

# 定义变量

FILE_DIR=/home/yarn/bash01/dir1

# "Hello YARN!"写入文件yarn1.txt

echo  "Hello YARN!" > "$FILE_DIR"/yarn1.txt

# 显示文件内容:"Hello YARN!"

cat  "$FILE_DIR"/yarn1.txt

# "Hello MapReduce!"写入文件并覆盖原内容"Hello YARN!"

echo  "Hello MapReduce!" > "$FILE_DIR"/yarn1.txt

# 显示文件内容:"Hello MapReduce!"

cat  "$FILE_DIR"/yarn1.txt

 

# 向文件中写入内容:"Hello YARN!"

echo  "Hello YARN!" > "$FILE_DIR"/yarn2.txt

cat  "$FILE_DIR"/yarn2.txt

# 向文件中追加内容:"Hello MapReduce!",在文件尾部添加,不覆盖原内容

echo  "Hello MapReduce!" >> "$FILE_DIR"/yarn2.txt

# 显示内容:

# Hello  YARN!

# Hello  MapReduce!

cat  "$FILE_DIR"/yarn2.txt

 

exit 0

 

9.6.2       输入重定向

   输入标准设备是键盘,输入重定向是将从标准输入设备读取数据重定向到其他媒介,通常情况下,最常见的是重定向输入到文件,从文件中读取数据到命令,和输出重定向正好相反。命令格式如下:

命令 < filename

  

   为了测试方便,创建一个文本文件file.txt,文件内容如下:

abc

efg

hello  java

shell  script

linux

hadoop

100 ok

50 aaa

355 399

25 22  19

99 1000  345 33

 

   例子:使用输入重定向符<将文件内容输出到命令

more  < file.txt

more  file.txt

cat  < file.txt

cat  file.txt

sort  < file.txt

sort -r  < file.txt

sort  file.txt

sort -r  file.txt

 

   双小于号<<是内联输入重定向符,使用方式和输入重定向符<不同,必须要给定结束符,并且不是从文件中直接读取数据。通常的用法如下面的例子,

   例:将控制台输入的信息重定向输入到文件

cat  > file << end

将控制台的输入

重定向到文件file

end

 

9.7           管道

   管道可以将前一个命令的输出作为后一个命令的输入,管道符是竖线|,用法比较简单,下面通过例子说明。

    ls命令的输出排序:

ls -a |  sort

ls |  grep f

cat  file.txt | grep java

cat  file.txt | sort -r | grep java

 

   rpm命令管理通过Red Hot包管理系统安装到当前系统上的软件包,参数-qa会在控制台显示已经安装的软件包,显示在控制台上的软件包没有排序,要想确认某个软件是否安装,在众多的安装软件中查找不太方便,如果将rpm命令的输出再进行排序,显示到控制台的安装软件会按照字典排序排列,可以通过管道命令实现排序,如下命令:

rpm -qa

rpm -qa  | sort

 

   控制台显示:(无排序)

iwl5150-firmware-8.24.2.2-1.el6.noarch

iwl6000g2a-firmware-17.168.5.3-1.el6.noarch

plymouth-system-theme-0.8.3-27.el6.centos.noarch

xorg-x11-fonts-Type1-7.2-9.1.el6.noarch

m17n-contrib-maithili-1.1.10-4.el6_1.1.noarch

wqy-zenhei-fonts-0.9.45-3.el6.noarch

...

 

控制台显示:(已排序)

usermode-gtk-1.102-3.el6.i686

ustr-1.0.4-9.1.el6.i686

util-linux-ng-2.17.2-12.9.el6.i686

valgrind-3.8.1-3.2.el6.i686

vconfig-1.9-8.1.el6.i686

vim-common-7.2.411-1.8.el6.i686

vim-enhanced-7.2.411-1.8.el6.i686

...

 

   安装软件很多的情况下,可以一屏一屏的显示,如下命令:

rpm -qa  | sort | more

 

   如果目录下文件很多,可以采用下面的命令让文件一屏一屏的显示:

ls -l |  more

 

9.8           数学运算

   Shell脚本中,算术运算和其他语言比较相对复杂,可以通过两种方式进行算术运算。

9.8.1       expr命令

   可以通过expr命令进行算术运算并返回结果,命令格式如下。

expr 5  + 5

expr 5 –  5

expr 5  / 5

expr 5  \* 5

 

   控制台显示:

10

0

1

25

 

   可以通过反引号将结果保存到变量中,在脚本中就可以引用变量了,如下所示:

   例:使用返引号或$()方式


 

var1=`expr 5 + 5`

var2=`expr 5 - 5`

var3=`expr 5 / 5`

var4=`expr 5 \* 5`

echo "$var1"

echo $var2

echo  ${var3}

echo  $var4

 

v=$(expr  20 + 2)

v=$(expr  20 - 2)

v=$(expr  20 / 2)

v=$(expr  20 \* 2)

echo $var1

echo $var2

echo $var3

echo  $var4

 

   expr命令支持的运算符说明:

操作符

说明

OP1 +  OP2

返回算术运算和

OP1 -  OP2

返回算术运算差

OP1 \*  OP2

返回算术运算乘积

OP1 /  OP2

返回算术运算商

OP1 %  OP2

返回算术运算余数

OP1  > OP2

如果OP1大于OP2,返回1,否则返回0

OP1  >= OP2

如果OP1大于等于OP2,返回1,否则返回0

OP1  < OP2

如果OP1小于OP2,返回1,否则返回0

OP1  <= OP2

如果OP1小于等于OP2,返回1,否则返回0

OP1 !=  OP2

如果OP1不等于OP2,返回1,否则返回0

OP1 =  OP2

如果OP1等于OP2,返回1,否则返回0

substr string pos length

返回string的从pos开始length长度的字符串

index string chars

返回charsstring中的开始位置,没有返回0

length string

返回字符串的长度

 

   注意:大于号和小于号需要转义,如下所示:

expr 5 \>  5

expr 5 \>=  5

expr 5 \<  5

expr 5 \<=  5

expr 5 !=  5

expr 5 =  5

 

   几个常用的字符串操作,如下所示:

expr substr abcdefg 2 3  # bcd

expr index abcdefg cd   # 3

expr length abcdefg      # 7

 

   控制台输出:

bcd

3

7

 

9.8.2       使用方括号

   另一种方式是采用方括号进行算术运算,如$[算术表达式],通常的方式将结果值赋给一个变量,使用方式如下。

   使用方括号进行算术运算:

   例子:

#!/bin/bash

 

var1=$[ 5 + 5 ]

var2=$[ 5 - 5 ]

var3=$[ 5 * 5 ]

var4=$[ 5 / 5 ]

var5=$[ 5 % 5 ]

var6=$[ 5 + ( 5 * 5 )]

 

echo "var1: $var1"

echo "var2: $var2"

echo "var3: $var3"

echo "var4: $var4"

echo "var5: $var5"

echo  "var6: $var6"

 

   控制台输出:

var1: 10

var2: 0

var3: 25

var4: 1

var5: 0

var6:  30

 

   注意:BashShell只支持整数运算,不支持小数运算。

9.9           脚本退出

   Linux中,每个命令执行完成后都会返回一个退出状态码,通知系统命令执行完成。退出状态码是0~255的一个正整数,在命令执行结束后由命令传给解释程序。通常情况下,可以根据这个值来判断命令是否执行成功。

   Linux提供了变量$?来保存最近执行的命令的退出状态码,当一个命令执行完成后,可以通过$?变量查看到命令的退出码,如下:

   例:通过变量$?查看命令执行的退出码

cd  /home/yarn/bash01

echo "$?"

mkdir  logs

echo "$?"

  

   常见的命令退出状态码:

状态码

说明

0

命令成功结束

1

未知错误

2

误用Shell命令

126

命令不可执行

127

命令没有找到

128

无效退出参数

 

   一个命令成功执行结束返回的状态码是0,如果命令执行出现错误会返回一个非0的正整数。

9.9.1       exit命令

   如果只在命令提示符下键入exit,将退出当前进程。如果在脚本中使用exit命令,会将exit命令之前的一个命令退出码返回。通常情况下:退出状态为0表示命令执行成功,而非0退出码说明命令没有执行成功。

当然,可以使用exit命令在脚本中指定退出码,如下所示。

cd  /home/yarn/bash01

mkdir  logs

exit 0

 

   命令exit可以在分支语句中使用,根据条件退出脚本执行并返回退出码,在父脚本中作为判断的条件。注意:退出码可以指定,但不要超过取值范围。退出码的取值范围是0~255,如果超出范围会返回256的余数。

if 创建目录

then

   cd 目录

   创建文件

else

   exit 1

fi

 

   例子:在命令行上测试命令执行后的退出码

mkdir  d1

echo $?

mkdir  d1

echo $?

cd d1

echo  "执行结束"

 

   例子:在脚本中,返回的退出码是最后一条命令的退出码

#!/bin/bash

#测试exit命令返回退出码

 

mkdir  d1

echo $?

mkdir  d1

echo $?

cd d1

echo  "执行结束"

exit 10

 

9.10      命令执行顺序控制

   通常情况下,一个脚本完成一个特定的任务,如:创建目录,然后在目录中创建日志文件。或者将文件备份后删除原文件,否则可能会丢失文件。也就是在执行某个命令的时需要依赖前一个命令是否执行成功。

如果希望在成功地执行一个命令之后再执行另一个命令,或者在一个命令失败后再执行

另一个命令,&&||可以完成这样的功能。相应的命令可以是系统命令或脚本。

另外,使用(){ }还可以在当前脚本中执行一组命令。

9.10.1    使用&&执行命令序列

   使用&&的一般形式为:

命令1  && 命令2 &&  命令3

 

当命令1返回真后(返回0,成功被执行),命令2才能够被执行,否则,&&后面的命令都不会执行。当命令2返回真后才会执行命令3,否则不会执行命令3。就如Java中的短路与相同。

例子:使用&&执行多个命令序列

[yarn@YARN  test]$ mkdir dir1 && echo "创建目录成功"

创建目录成功

[yarn@YARN  test]$ mkdir dir1 && echo "创建目录成功"

mkdir: 无法创建目录"dir1": 文件已存在

 

[yarn@YARN  test]$ cd /home/yarn && mkdir logs && cd logs &&  touch log.log && pwd

/home/yarn/logs

 

目录创建成功,后面的命令才会执行输出。第二个命令序列也是一样,前面的命令执行成功,后面的命令才会继续执行。

命令序列通常使用在if语句中,如果命令序列执行全部成功则执行分支中的语句。如果全部执行成功退出码为0,否则为非0if语句根据退出码判断执行分支语句。

9.10.2    使用||执行命令序列

使用||的一般形式为:

命令1 || 命令2 || 命令3

 

当命令1执行失败(返回非0的值),命令2才能够被执行,否则,||后面的命令都不会执行。当命令2执行也失败了才会执行命令3,否则不会执行命令3。就如Java中的短路或相同。

例子:使用||执行多个命令序列

#!/bin/bash

# 错误信息不输出

exec  2>/dev/null

 

cp  file1 file2 || echo "文件拷贝失败"

 

例子:如果目录存在,移动到临时目录,然后继续创建

mkdir dir1 || mv dir1 /tmp/dir1 && mkdir  dir1

 

例子:命令也可以是脚本,首先创建一个脚本。

#!/bin/bash

# 脚本中的错误信息忽略

exec  2>/dev/null

# 切换到目录

cd  /home/yarn

# 拷贝不存在的文件

cp file1 file2

 

执行脚本:

[yarn@YARN test]$ t.sh || "文件拷贝失败"

-bash: 文件拷贝失败: command not found

 

9.10.3    使用(){}执行命令序列

如果想执行一组命令,可以采用两种方式,将命令组放在(){}中,如果在一行需要使用命令分隔符;将命令分开,如果不在一行,就不需要了。这两种方式的区别是,()可以在命令行上使用,而{}方式只能在脚本中使用。另外,()方式执行脚本不会改变当前命令行状态。

例子:在命令行执行()语句块

[yarn@YARN  test]$ (cd /home/yarn;pwd)

/home/yarn

[yarn@YARN test]$

 

另外,这两种方式的区别是,()方式可以引用父脚本中的变量,但不能改变变量的值,而{}方式可以引用父脚本中的变量,也可以改变变量的值。

例子:两种方式引用父进程中的局部变量,并修改变量的值,比较两种方式的区别。

#!/bin/bash

#测试()和{}

name=zhangsan

(

echo $name

name=lisi

echo $name

echo "()中的进程号: $$"

)

 

echo $name

echo "bash03.sh进程号:$$"

 

 

 

 

#!/bin/bash

name=zhangsan

{

echo $name

name=lisi

echo $name

echo "{}进程号:$$"

}

echo $name

echo "bash04.sh进程号:$$"

 

需要注意的是,这两种方式不会启动一个新的子进程,在子进程中执行命令,而是都在父进程中执行。

例子:在脚本中使用两种方式执行,验证是否创建了新的子进程。


 

另外,在()内声明的变量,在脚本中无法访问,相当于局部变量,而在{}内声明的变量在脚本中可以访问

例子:在()中声明的变量类似局部变量,脚本中不能访问。在{}中声明的变量,脚本中可以访问。


 

这两种方式通常使用的场景是重定向输入和重定向输出,从一个文件中读取或重定向输出到文件。

例子:使用两种方式重定向输入和重定向输出到文件。

#!/bin/bash

echo "脚本开始执行"

(

cd /home/yarn/b3

echo "切换到/home/yarn/b3目录下"

mkdir d6

echo "创建目录d6"

cd d6

echo "切换到d6"

touch log.log

echo "创建日志文件log.log"

 

 

)>f2

 

将代码块{…}中的所有标准输出内容都重新定向到文件中,注意,只有标准输出到控制台的内容才会写入到文件中。

   例:代码块的用法。

#!/bin/bash

#  {}>file

 

FILE_DIR=/home/yarn/bash01/dir1

 

# 代码块

{

 # 判断变量是否是目录

 if [ -d "$FILE_DIR" ]

 then

    # 标准输出到控制台的信息,所以会写到文件中

echo "Add content to the  '$FILE_DIR/yarn3.txt'"

# 标准输出已经重定向到文件中了,所以不会显示到控制台,也不会写到文件中

echo "Bash Sheel!" >>  "$FILE_DIR"/yarn3.txt

# 同上

    echo "Linux!" >>  "$FILE_DIR"/yarn3.txt

 fi

# 重新定向到文件中,可以作为日志

} >  "$FILE_DIR"/bashbak1

# 显示文件内容:Add content to the  '/home/yarn/bash01/dir1/yarn3.txt'

cat  "$FILE_DIR"/bashbak1

 

从文件中输出内容。

   例:read命令的用法例子。

#!/bin/bash

# <

 

FILE_DIR=/home/yarn/bash01/dir1/yarn2.txt

# 从文件中读取第一行到变量line1

read line1  < "$FILE_DIR"

# 还是读取第一行

read  line2 < "$FILE_DIR"

# 两个变量的内容都是文件的第一行内容:Hello YARN!

echo  "$line1"

echo  "$line2"

 

# 代码块{…}中读取两次到两个变量,变量line3读取的是第一行

# 变量line4读取的是文件的第二行

{

  read line3

  read line4

} <  "$FILE_DIR"

 

echo

# 输出:Hello YARN!

echo  "$line3"

# 输出:Hello MapReduce!

echo  "$line4"

 

9.11       grep命令

grep命令使用广泛,允许对文本文件中的内容进行模式查找。如果找到匹配模式的信息会输出到控制台,打印符合条件的所有行。创建测试文件flowlist.txt

flowlist.txt文件:

1501010001        71.05 2141618     3       07/11/15  21:46:57        1006 User001     109   4       303

1501010002        865.86        1686831     2       05/11/15  03:52:44        1009 User009     109   1       302

1501010003        652.61        2587675     2       07/14/15  19:17:39        1010 User003     110   1       305

1501010004        905.24        1282788     1       04/17/15  17:14:09        1010 User004     102   3       304

1501010005        444.25        1624680     1       04/05/15  11:40:51        1005 User009     108   1       308

1501010006        48.21 2473714     3       01/18/15  23:45:51        1001 User003     110   2       302

1501010007        396.26        2512994     4       08/07/15  05:55:16        1005 User009     102   2       308

1501010008        690.74        1259159     2       08/06/15  02:48:20        1004 User001     104   4       304

1501010009        122.37        2462139     1       04/27/15  08:19:22        1008 User008     105   2       310

 

grep命令格式:

grep [参数] 查找的内容 [文件]

 

例子:查找所有以txt结尾的文件中存在字符串的记录并显示到控制台。

[yarn@YARN test]$ grep "User009" *.txt

1501010002      865.86   1686831 2       05/11/15  03:52:44       1009    User009  109     1302

1501010005      444.25   1624680 1       04/05/15  11:40:51       1005    User009  108     1308

1501010007      396.26   2512994 4       08/07/15  05:55:16       1005    User009  102     2308

 

例子:查找存在字符串的行数。

[yarn@YARN test]$ grep -c "1005" flowlist.txt

2

 

例子:查找存在字符串的记录及行号。

[yarn@YARN test]$ grep -n "1005" flowlist.txt

5:1501010005    444.25  1624680 1       04/05/15 11:40:51       1005    User009 108     1308

7:1501010007    396.26  2512994 4       08/07/15 05:55:16       1005    User009 102     2308

 

例子:显示不包含条件的记录。

[yarn@YARN test]$ grep -v "1005" flowlist.txt

1501010001      71.05    2141618 3       07/11/15  21:46:57       1006    User001 109     4303

1501010002      865.86   1686831 2       05/11/15  03:52:44       1009    User009 109     1302

1501010003      652.61   2587675 2       07/14/15  19:17:39       1010    User003 110     1305

1501010004      905.24   1282788 1       04/17/15  17:14:09       1010    User004 102     3304

1501010006      48.21    2473714 3       01/18/15  23:45:51       1001    User003 110     2302

1501010008      690.74   1259159 2       08/06/15  02:48:20       1004     User001 104     4304

1501010009      122.37   2462139 1       04/27/15  08:19:22       1008    User008 105     2310

 

例子:精确查找符合条件的记录。

[yarn@YARN test]$ grep "303" flowlist.txt

1501010001      71.05    2141618 3       07/11/15  21:46:57       1006    User001 109     4 303

 

例子:忽略大小写查询。

[yarn@YARN test]$ grep -i "user001" flowlist.txt

1501010001      71.05    2141618 3       07/11/15  21:46:57       1006    User001  109     4303

1501010008      690.74   1259159 2       08/06/15  02:48:20       1004    User001  104     4304

 

例子:将查询结果通过管道继续查询符合条件的记录。

[yarn@YARN test]$ grep "User001" flowlist.txt | grep "304"

1501010008      690.74   1259159 2       08/06/15  02:48:20       1004    User001  104     4304

 

例子:将查询结果输出到文件。

[yarn@YARN test]$ grep "304" flowlist.txt > file_304.txt

[yarn@YARN test]$ cat file_304.txt

1501010004      905.24   1282788 1       04/17/15  17:14:09       1010    User004 102     3304

1501010008      690.74   1259159 2       08/06/15  02:48:20       1004    User001 104     4304

 

例子:查询用户信息。

[yarn@YARN test]$ grep "yarn" /etc/passwd

yarn:x:500:500:yarn:/home/yarn:/bin/bash

[yarn@YARN test]$ grep yarn /etc/passwd

yarn:x:500:500:yarn:/home/yarn:/bin/bash

 

例子:grep命令不仅可以在文件查找符合条件的行,也可在字符串中查找符合条件的子串。

[yarn@YARN test]$ echo "Hello Linux" | grep in

Hello  Linux

[yarn@YARN test]$ echo "I Study Hadoop" | grep "doo"

I  Study Hadoop

 

9.12      变量设置模式

9.12.1   检查变量是否已经设置值

   测试变量是否已设置值或被初始化。如果变量未被初始化,则返回value

   命令格式如下:

var=${var:-value}

var=${var-value}

  

   例子:测试变量是否被初始化,如果未被初始化,在返回一个默认的值。

# 变量name未设初值则返回默认值

[yarn@YARN test]$ echo ${name:-zhangsan}

zhangsan

# 可以看出,虽然返回了默认值,但是变量name并未赋值

[yarn@YARN test]$ echo ${name:-lisi}

lisi

# 为变量name赋值

[yarn@YARN test]$ name=wangwu

# 因为变量name已经赋值,所以返回变量的值

[yarn@YARN test]$ echo ${name:-zhangsan}

wangwu

# 清除变量name

[yarn@YARN test]$ unset name

# 变量那么未赋初值,返回默认值

[yarn@YARN test]$ echo ${name:-zhangsan}

zhangsan

 

测试变量是否已设置值或被初始化。如果变量未被初始化,则使用另一个设定的值。与上面命令不同之处在于,将默认值赋值给变量。

   命令格式如下:

var=${var:=value}

 

例子:测试变量是否被初始化,如果未被初始化,在返回一个默认的值并将默认值赋值给变量。

# 变量id未被赋值,返回默认值并且将值赋给变量id

[yarn@YARN test]$ echo ${id:=1001}

1001

# 因为变量id已经赋值1001,所以直接返回

[yarn@YARN test]$ echo ${id:=1002}

1001

# 清除变量id

[yarn@YARN test]$ unset id

# 变量id未赋值,返回默认值1002并赋值给变量id

[yarn@YARN test]$ echo ${id:=1002}

1002

# 显示变量id的值,

[yarn@YARN test]$ echo $id

1002

# 因为变量id已经赋值,所以直接返回已有的值

[yarn@YARN test]$ echo ${id:=1003}

1002

 

测试变量是否已设置值或被初始化。如果变量未被初始化,则返回一个空串。如果已设置初始值,则将新值返回但并不赋值给变量。

   命令格式如下:

var=${var:+value}

 

例子:测试变量是否被初始化,如果未被初始化,在返回一个空串。否则,返回新值但不会将新值赋值给变量。

# 变量price未被赋初值,返回空串

[yarn@YARN test]$ echo ${price:+100}

 

# 如果变量price未赋值,将新值200赋值给变量price

[yarn@YARN test]$ price=${price:-200}

# 打印变量的值,为200

[yarn@YARN test]$ echo $price

200

# 如果变量已经赋值,则返回新值,但是并不会将新值赋给变量price

[yarn@YARN test]$ echo ${price:+100}

100

# 打印变量price,变量的值还是旧值200

[yarn@YARN test]$ echo $price

200

 

9.12.2    设置只读变量

   在声明变量并赋值后,使用readonly命令修饰,变量就成了只读变量,在整个生命周期内不能修改了。

   例子:将变量声明为只读变量。

# 声明变量并赋值

[yarn@YARN test]$ name=zhangsan

# 将变量设置成只读变量

[yarn@YARN test]$ readonly name

# 打印变量的值

[yarn@YARN test]$ echo $name

zhangsan

# 为变量重新赋值,提示为只读不能修改

[yarn@YARN test]$ name=lisi

-bash: name:  readonly variable

# 变量声明时设置为只读

[yarn@YARN test]$ readonly id=1001

[yarn@YARN test]$ echo $id

1001

# 为变量重新赋值,提示为只读变量

[yarn@YARN test]$ id=1002

-bash: id: readonly variable