Shell脚本自学笔记整理

提示:
本文内容 引用 《Shell 脚本学习指南》大家有兴趣可以入手
部分使用菜鸟教程的知识点作为补充
还有一些自己的理解,以及众多博客文


文章目录

  • 前言
  • 一、Shell是什么?
  • 二、入门
    • 2.1位于第一行的 #!
    • 2.2 Shell 的基本元素
      • 2.2.1 命令与参数
      • 2.2.2 变量
      • 2.2.3 输出打印语句
      • 2.2.4 基本的 I/O 重定向
        • 2.2.4.1 重定向与管道
        • 2.2.4.2 特殊文件:/dev/null 与 /dev/tty
      • 2.2.5 基本命令查找
    • 2.3 访问Shell 脚本的参数
    • 2.4 简单的执行跟踪
  • 三:查找与替换
    • 3.1 查找文本
      • 3.1.1 简单的grep
    • 3.2 正则表达式
      • 3.2.1 什么是正则表达式
        • 3.2.1.1 POSIX 方括号表达式
      • 3.2.2 基本正则表达式
      • 3.2.3 扩展正则表达式
      • 3.2.4 正则表达式的扩展
  • 六 变量、判断、重复动作
      • 6.1.1 变量赋值与环境
        • 6.1.2.1 展开运算符
        • 6.1.2.2 位置参数
    • 6.2 退出状态
      • 6.2.1 退出状态值
      • 6.2.2 if-elif-else-fi 语句
      • 6.2.4 test命令
    • 6.3 case 语句
    • 6.4 循环
      • 6.4.1 for 循环
      • 6.4.2 while 与 until循环
      • 6.4.3 break 与 continue
      • 6.4.4 shift与选项的处理
    • 6.5 函数
  • 七 输入/输出、文件与命令执行
    • 7.1 标准输入、标准输出与标准错误输出
    • 7.2 使用 read 读取行
  • 总结


前言

对于Shell 脚本,我自己并没有系列的学习过,大都是在工作中,现学现卖。
基本上都要实现某一需求,去找相关的资料,再去整合,编写和测试。
对于服务器来说,如果掌握了Shell 其实能更好的提高效率,有些需求,比如在服务器上对文件的操作,对应用程序的自动备份等等,使用Shell 脚本,效率就很高。
因此,结合自己工作经验和Shell 脚本学习指南,就自己的Shell 知识进行归纳整理
当然因为是我自己整理的笔记,只根据自己的理解和需要 做的,有些没摘抄或者不全的,大家自己查查


提示:以下是本篇文章正文内容,下面案例可供参考

一、Shell是什么?

Shell 与awk 、Perl 、Python、Ruby 都是脚本编程语言。他们的优点就是,多半运行在比编译型语言还高的层级,能够轻易处理文件和目录之类的对象。缺点:效率通常不如编译型语言。

之所以使用Shell 脚本 是基于:

简单性:Shell 是高级语言;通过它,可以简洁表达复杂的操作。
可移植性:使用POSIX的标准所定义的功能,可以做到脚本无须修改就可在不同的系统上执行。
开发容易:可以在短时间内完成一个功能强大又好用的脚本。

二、入门

2.1位于第一行的 #!

当一个文件中开头的两个字符是 #!时,内核会扫描该行其余部分,看是否参在可用来执行程序的结束器的完整路径。(中间如果出现任何空白符号都会略过。)此外,内核还会扫描是否有一个选项要传递给解释器。内核会以指定的选项来引用解释器,再搭配命令的其他部分。

假设有个 csh 脚本,名为/usr/ucb/whizprog ,它的第一行所示:

#! /bin/csh -f

再者,如果Shell 的查找路径里有 /usr/ucb ,当用户输入 whizprog -q /dev/tty01 这条命令,内核解释 #! 这行后,便会以如下的方式来引用 csh :

/bin/csh -f /usr/ucb/whizprog -q  /dev/tty01

这样的机制让我们得以轻松地引用任何解释器。例如我们可以这样引用独立地 awk 程序

#! /bin/awk -f

简单理解就是 #! 开头,系统会直接去找合适的解释器

Shell 脚本通常一开始都是 #! /bin/sh 开头.如果你的 /bin/sh 并不符合POSIX标准,请将这个路径改为符合POSIX标准的Shell。下面是几个初级的陷阱,请特别留意:

  • 当今系统 对 #! 这一行的长度有限制从63到1024个字符读。建议不要超过64个字符
  • 在某些系统上,命令行部分(也就是要传递给解释器执行的命令)包含了命令的完整路径名称。不过有些系统却不是这样;命令行的部分会原封不动地传给程序。因此,脚本是否具有可移植性取决于是否有完整地路径名称
  • 别在选项之后放置任何空白,因为空白也会跟着选项一起传递给被引用地程序
  • 要知道解释器地完整路径名称。可以解决可移植性问题,不同厂商可能将同样地东西放在不同地地方
  • 一些旧地系统上,内核不具备解释#! 的能力,有些Shell 会自行处理,这些Shell对于 #! 与紧随其后的解释器名称之间是否可以有空白,可能有不同的解释。

2.2 Shell 的基本元素

2.2.1 命令与参数

Shell 识别三种基本命令:内建命令,Shell函数以及外部命令
命令的原理
1.格式简单 以空白(Space 键或Tab 键)隔开命令行中各个组成部分
2.命令名称是命令行第一个项目,后面通常跟着选项,任何额外的参数都会放在选项之后
3.选项的开头是一个破折号(或减号),后面接着一个字母


2.2.2 变量

变量就是为某个信息片段所起的名字
变量名称开头是一个字母或者下划线符号,后面接任意长度的字母、数字、或下划线。长度无限制

赋值方式:变量名称=字符,中间完全没有任何空白。
当你想取出Shell 变量值时:$变量名
当所赋予的值内含空格时,请加上引号

first=isaac midedle=bash last=singer //单行可进行多次赋值
fullname1="isaac bash singer" //值中包含空格时使用引号
oldname=$fullname1 //此处不需要引号
fullname2="$first $middle $last" //这里需要双引号

2.2.3 输出打印语句

没什么可说的,输出语句

echo 语句
echo加空格 加输出内容

$ echo 输出的测试语句
输出的测试语句

printf 语句

$ printf "输出的测试语句"
输出的测试语句

2.2.4 基本的 I/O 重定向

2.2.4.1 重定向与管道

以 < 改变标准输入
program < file 可将 program 的标准输入修改为 file

tr -d '\r' < dos-file.txt ...

以 > 改变标准输出
program > file 可将program 的标准输出修改为file

tr -d '\r' < dos-file.txt > UNIX-file.txt
//这条命令会以tr 将 dos-file.txt 里的 ASCII 回车删除,再将转换完成的数据输出到 UNIX-file.txt。dos-file.txt里的原始数据不会有变化。

“>” 重定向符 在目的文件不存在时,会新建一个。然而,如果目的文件已存在,它就会被覆盖;原本的数据会丢失。

以 >> 附加到 文件

program >> file 可将program 的标准输出附加到file 的结尾处

for f in dos-file*.txt
do
	tr -d '\r' < dos-file.txt >> UNIX-file.txt
done

如">" 在目的文件不存在时,会新建一个。然而,如果目的文件已存在,它就不会被覆盖,而是将数据所产生的数据附加到文件结尾处。

以 | 建立管道
program1 | program2 可将program1 的标准输出修改为program2 的标准输入

tr -d '\r' < dos-file.txt | sort > UNIX-file.txt
//先删除输入文件内的回车字符,在完成数据的排序后,将结果输出到目的文件

管道可以把两个以上执行中的程序衔接在一起。第一个程序的标准输出修改为第二个程序的标准输入,管道的效率比使用临时文件的程序快十倍。

tr 命令

tr [option] source-char-list replace-char-list
用途:转换字符。 eg:将 大写字符 转换成小写。选项可以让你指定所要删除的字符,以及将一串重复出现的字符浓缩成一个。

选项 作用
-c 取source-char-list 的反义。tr 要转换的字符,变成未列在source-char-list 中的字符。
-C 与-c 相似,但所处理的字符(可能时包含多个字节的宽字符),而非二进制的字节值。
-d 自标准输入删除source-char-list 中的字符,而不是转换它们。
-s 浓缩重复的字符。如果标准输入中连续重复出现在source-char-list 中的字符,则将其浓缩成一个。

2.2.4.2 特殊文件:/dev/null 与 /dev/tty

第一个文件 /dev/null : 位桶 传送到此文件的数据都会被系统丢掉
我一直接理解成 垃圾桶,不能回收的回收站

第二个文件 /dev/tty :/dev/tty如果一个控制台有一个终端的话,那么这个文件就是对应的当前的这个控制终端的别名。当程序打开此文件是,Linux会自动将它重定向到一个终端窗口[一个实体的控制台(console)或者串行端口(serial port),也可能是一个通过网络与窗口登陆的为终端(pseudterminal)],因此该文件对于读取人工输入时(例如密码)特别有用。

一个校验密码的例子eg:

#! /bin/sh
printf "Enter your password: "
stty -echo
read passwd1 < /dev/tty
printf "\nEnter your password again: "
read passwd2 < /dev/tty
stty echo
 
if [ "$passwd1" = "$passwd2" ]
then
    passwd=$passwd1
    printf "\nPassword set successfully!\n"
else
    printf "\nThe two passwords you typed do not match!\n"
fi

stty(set tty) 命令是用来控制终端的,stty -echo表示关闭自动打印每个输入字符的功能,stty echo表示恢复自动打印每个输入字符的功能。运行上面的脚本,在终端上并不会显示输入的密码,保证了密码的安全性。


2.2.5 基本命令查找

Shell 会沿着查找路径 $ PATH 来寻找命令。$ PATH 是一个以冒号分割的目录列表,可以在列表所指定的目录下找到所要执行的命令。
默认路径因系统而异,不过至少包括/bin 与 /usr/bin ,以及供本地系统安装人员安装程序的 /usr/local/bin

$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:
//bin 的目录用来保存可执行文件

如果你要编写自己的脚本,最好准备自己的bin 来存放它们,并且让Shell 能够自动找到它们。这不难,只要建立自己的bin 目录,并将它加入 $ PATH中的列表即可。
(这个在实际操作中挺有用的)

$ cd 自己的目录下(/home 或者 自己的用户目录下 /home/username)
$ mkdir bin //建立个人bin 目录
$ mv testsh bin //将我们的脚本放入该目录
$ PATH=$PATH:$HOME/bin //将个人的bin 目录附加到PATH
$ testsh       //执行 

要让修改永久生效,在 .profile 文件中把你的bin 目录加入 $PATH ,每次登录时Shell 都将读取 .profile 文件

补充知识,$PATH 里的空项目表示当前目录。

空项目于路径值中间时,可以用两个连续的冒号来表示。如果将冒号直接置于最前端或尾端,可以分别表示查找时最先查找或最后查找当前目录:

PATH=:/bin:/usr/bin:/usr/local/bin 先找当前目录
PATH=/bin:/usr/bin:/usr/local/bin: 最后找当前目录
PATH=/bin:/usr/bin::/usr/local/bin 当前目录居中

如果你希望将当前目录纳入查找路径,更好的做法是使用点号;测试中,发现空项目 在同一系统不同的版本中并未正确支持,因此空项目在可移植性上有问题。空项目存在安全问题,不建议使用!!!


2.3 访问Shell 脚本的参数

位置参数指的也就是Shell 脚本的命令行参数。在Shell函数里,它们也可以是函数的参数。各参数都由整数来命名。基于历史原因,当它超过9时,就应该用大括号把数字框起来:

详情可见后面章节


2.4 简单的执行跟踪

可以在脚本里,用set -x 命令将执行跟踪的功能打开,然后再用set +x 命令关闭它

$ sh -x testEcho.sh  //开启执行追踪

$ vi testEcho2.sh  //新建testEcho2.sh 脚本 进行开追踪和关追踪
#! /bin/sh
set -x
echo lst echo

set +x
echo 2nd echo

$ ./testEcho2.sh //直接执行就可以看见追踪信息了

三:查找与替换

3.1 查找文本

以grep 程序查找文本

  • grep 最在的文本匹配程序,使用POSIX 定义的基本正则表达式
  • egrep 扩展式grep(Extended grep),有更强大的正则表达式
  • fgrep 快速grep (Fast grep) 匹配固定字符串而非正则表达式

3.1.1 简单的grep

grep 匹配固定的字符串

grep hello testEcho.sh //查找testEcho.sh 里的hello

3.2 正则表达式

grep 的语法: grep [option …] pattern-spec [files …]
显示匹配一个或多个模式的文本行

选项 作用
-E 可取代 egrep
-F 使用固定字符串进行匹配。grep -F 可取代 fgrep
-e pat-list 通常,第一个非选项的参数会指定要匹配的模式。
-f pat-file 从 pat-file 文件读取模式匹配
-i 模式匹配时忽略字母大小写差异
-l 列出匹配模式地文件名称
-q 静默的。如果模式匹配匹配,则grep 会成功地离开,而不将匹配地行写入标准输出;否则即是不成功
-s 不显示错误信息。通常与-q 并用
-v 显示不匹配模式的行

3.2.1 什么是正则表达式

正则表达式时由两个基本组成部分所建立:一般字符和特殊字符。一般字符指的是任何没有特殊意义的字符,某些情况下,特殊字符也可以视为一般字符。特殊字符常称为元字符,本章接下来都会以meta字符表示。

POSIX的全称是Portable Operating System Interface for uniX,它由一系列规范构成,定义了UNIX操作系统应当支持的功能,所以“POSIX规范的正则表达式”其实只是“关于正则表达式的POSIX规范”,它定义了BRE(Basic Regular Expression,基本型正则表达式)和ERE(Extended Regular Express,扩展型正则表达式)两大流派。在兼容POSIX的UNIX系统上,grep和egrep之类的工具都遵循POSIX规范,一些数据库系统中的正则表达式也符合POSIX规范。

下为POSIX BRE与ERE的meta字符

字符 BRE/ERE 模式含义
\ 两者都可 通常用以关闭后续字符的特殊意义。有时则是相反地打开后续字符地特殊意义
. 两者都可 匹配任何单个地字符,但NULL 除外
* 两者都可 匹配在它之前地任何数目(或没有)地单个字符
^ 两者都可 匹配紧接着的正则表达式,在行或字符串的起始处。BRE:仅在正则表达式的开头处具有此特殊含义,ERE::置于任何位置都具有特殊含义
$ 两者都可 匹配前面的正则表达式,在字符串或行结尾处。BRE:仅在正则表达式的结尾处具有此特殊含义,ERE::置于任何位置都具有特殊含义
[…] 两者都可 方括号表达式,匹配方括号内的任一字符。
\{n,m \} BRE 区间表达式,匹配在它前面的单个字符重现的次数区间。重现n至m次,m最小值为255
\( \) BRE 将\( 与 \)间的模式存储在特使的保留空间。最多可以将9个独立的子模式存储在单个模式中。匹配于子模式的文本,可通过转义序列\1 至\9,被重复使用在相同模式里。例如\(ab\).*\1,指的是匹配于ab组合的两次重现,中间可存在任何数目的字符
\n BRE 重复在 \( 与 \)方括号内第n 个子模式至此点的模式。n 为1 至9的数字,1为由左开始。
{n,m} ERE 与前面的 BRE 的\{n,m \} 一样,只不过没有反斜杠
+ ERE 匹配前面正则表达式的一个或多个实例
? ERE 匹配前面正则表达式的零个或一个实例
| ERE 匹配于 符号前或后的正则表达式
( ) ERE 匹配于方括号括起来的正则表达式群

简单的正则表达式范例

表达式 匹配
tolstoy 位于一行上任何位置的7个字母 :tolstoy
^ tolstoy 7个字母 tolstoy ,出现在一行的开头
tolstoy$ 7个字母 tolstoy ,出现在一行的结尾
^tolstoy$ 正好包括7个字母 tolstoy,没有其他的任何字符
[Tt]olstoy 在一行上的任意位居中,含Tolstory 或是 tolstoy
tol.toy 在一行上的任意位居中,含tol这3个字母,加上任何一个字符,再接着toy 这3个字母
tol.*toy 在一行上的任意位居中,含tol这3个字母,加上任何任意的0或多个字符,再接着toy 这3个字母,例如: toltoy、tolstoy、tolWHOtoy等
3.2.1.1 POSIX 方括号表达式

POSIX 标准强化其字符集范围的能力,以匹配英文字母字符。

字符集、排序符号、等价字符集

类别 匹配字符
[:alnum:] 数字字符
[:alpha:] 字母字符
[:blank:] 空格(space)与定位(Tab)字符
[:cntrl:] 控制字符
[:digit:] 数字字符
[:graph:] 非空格字符
[:lower:] 小写字母字符
[:print:] 可显示的字符
[:print:] 可显示的字符
[:punct:] 标点符号字符
[:space:] 空白字符
[:upper:] 大写字母字符
[:xdigit:] 十六进制数字

3.2.2 基本正则表达式

3.2.3 扩展正则表达式

3.2.4 正则表达式的扩展

额外的GNU正则表达式运算符

运算符 含义
\w 匹配任何单词组成字符 ,等同于 [[:alnum:]]
\W 匹配任何非单词组成字符,等同于 [^[:alnum:]]
\<\> 匹配单词的起始 与 结尾
\B 匹配两个单词组成字符之间的空字符串
\b 匹配单词的起始或结尾所找到的空字符串。
\’ \` 分别匹配emacs缓冲区的开始与结尾。GNU通常视为与 ^ 及 $ 同义

六 变量、判断、重复动作

6.1.1 变量赋值与环境

export,readonly
语法 :
export name[=word]…
export -p
readonly name[=word] …
readonly -p
用途 :
export 用于修改或打印环境变量,readonly则使得变量不得修改
主要选项:
-p 打印命令的名称以及所有被导出(只读)变量的名称与值,这种方式可使得Shell重新读取输出以便重新建立环境(只读设置)

export 经常使用,可以着重看下

export: 用法是将变量放进环境里。环境是一个名称与值的简单列表,可供所有执行中的程序使用。

PATH=$PATH:/usr/local/bin     更新PATH
export PATH                   导出它

export -p                     显示当前的环境

export 的命令 在Linux 的时候中,第一次使用,就是配置JDK 的环境变量
export 相当于导出变量。" = "赋值符号定义的变量只在当前shell中可用,外部(子shell中)使用时,定义时需要用export A=“xxx”,或在使用时 使用export A。 如果希望下载软件后不加入路径就能启动该程序,要把可执行程序的路径加入 PATH 中

vi /etc/profile         打开配置 /etc/profile 文件中设置的变量是全局变量

其中加入
export JAVA_HOME=/usr/java-1.6.xxx    //java的目录
export PATH=$JAVA_HOME/bin:$PATH      //将java加入PATH中
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar   //更新CLASSPATH

最后sourse 下 
source /etc/profile      //使环境变量生效

6.1.2.1 展开运算符

第一组字符串处理运算符用来测试变量的存在状态,且为在某种情况下允许某种情况下允许默认值的替换。如下所示
替换运算符

运算符 替换
${varname:-word} 如果varname 存在且非null 则返回其值;否则,返回 word。
用途:如果变量未定义,则返回默认值
范例:如果count 未定义,则${count:-0}的值为0,count依旧不存在
${varname:=word} 如果varname 存在且非null 则返回其值;否则,设置它为word
用途:如果变量未定义,则设置变量为默认值
范例:如果count 未定义,则${count:-0}设置count为0 ,就可以使用count
${varname:?message} 如果varname 存在且非null 则返回其值;否则,显示 varname:message,并退出当前命令或脚本。省略message会出现默认信息 parameter null or not set.
用途:为了捕获由于变量未定义所导致的错误。
范例:${count:?“undefined!”}将显示 count:undefined!,且如果count未定义,则退出。
${varname:+word} 如果varname 存在且非null 则返回word;否则,返回null。
用途:为测试变量的存在
范例:如果count 已定义,则${count:+1}返回1 count赋值为0,返回也是1

模式匹配运算符
假设 path 的值为 /home/tolstoy/mem/long.file.name

运算符 替换
${variable#pattern} 如果模式匹配变量值的开头处,则删除匹配的最短部分,并返回剩下的部分
例:${path#/*/} 结果:tolstoy/mem/long.file.name
${variable##pattern} 如果模式匹配变量值的开头处,则删除匹配的最长部分,并返回剩下的部分
例:${path##/*/} 结果:long.file.name
${variable%pattern} 如果模式匹配变量值的结尾处,则删除匹配的最短部分,并返回剩下的部分
例:${path%.*} 结果:/home/tolstoy/mem/long.file
${variable%%pattern} 如果模式匹配变量值的结尾处,则删除匹配的最长部分,并返回剩下的部分
例:${path%%.*} 结果:/home/tolstoy/mem/long

#位置在键盘靠左,%位置在键盘靠右,所以#匹配前面,%匹配后面,这样更好记忆


6.1.2.2 位置参数

所谓位置参数,指的是Shell 脚本的命令行参数;同时也表示在Shell
函数内的函数参数。他们的名称以单个的整数来命名。当这个整数大于9时,就可以以花括号{} 括起来:
echo first arg is $1
echo tenth arg is ${10}

下面介绍特殊“变量”


$#    				   # 提供传递到Shell脚本或函数的参数总数 

示例:
while [ $# != 0 ]     #以shift 逐渐减少$#,循环将会终止
do 
	case $1 in        
	....			  #处理第一个参数
	esac
	shift			 #移开第一个参数
done 


$*,$@ #一次表示所有的命令行参数。这两个参数可用来把命令行参数传递给脚本或函数所执行的程序

"$*"  #将所有命令行参数视为单个字符串。等同于“$1 $2 ...”.

"$@"  #将所有命令行参数视为单独的个体,也就是单独字符串。 等同于"$1" "$2" ....。
	  #这是将参数传递给其他程序的最佳方式,因为它会保留所有内嵌在每个参数的任何空白。

set   #命令可以做的事很多。调用此命令而未给予任何选项,则它会设置位置参数的值,并将之前存在的任何值丢弃:

例子: 
set -- hi there how do you do   # -- 会结束选项部分,自hi 开始新的参数

shift #命令是用来“截去”来自列表的位置参数,由左开始。一旦执行shift,$1的初始值会永远消失,取而代之的是$2的旧值。$2的值,变成$3的旧值,以此类推。$#则会逐次减1。shift也可使用一个可选的参数,也就是要位移的参数的计数。
#单纯的shift 等同于 shift 1。

以下是综合范例

$ set -- hello "hi there" greetings    #设置新的位置参数
$ echo there are $# total arguments    #显示计数值
there are 3 total  arguments    


$ for i in $*                         #循环处理每一个参数
> do echo i is $i
> done
i is hello
i is hi
i is there
i is greetings


$ for i in $@                         #在没有双引号的情况下,$* 与 $@ 是一样的
> do echo i is $i
> done
i is hello
i is hi
i is there
i is greetings

$ for i in "$*"                         #加双引号的情况下,$* 表示一个字符串
> do echo i is $i
> done
i is hello hi there greetings


$ for i in "$@"                         #加双引号的情况下 $@ 保留真正的参数值
> do echo i is $i
> done
i is hello
i is hi there
i is greetings


$ shift                					#截去第一个参数
$ echo there are now $# arguments       #证明它已消失
there are now 2 arguments 

$ for i in "$@"                        
> do echo i is $i
> done
i is hi there
i is greetings



6.2 退出状态

6.2.1 退出状态值

以惯例来说,退出状态为0表示“成功”。内置变量 ? (以 $? 访问它) 包括了Shell 最近一次所执行的一个程序的退出状态

$ ls -l /dev/null       #ls一个存在的文件
crw-rw-rw- 1 root root 1,3 Aug 30 2020 /dev/null  #ls的输出
$ echo $?               #显示退出状态
0
$ ls test               #ls一个不存在的文件
ls报错......
$ echo $?              #显示退出状态
1
结束状态的值 意义
0 命令成功地退出
>0 在重定向或单次展开期间失败
1-125 命令不成功地退出。特定的退出值得含义,是由各个单独得命令定义的
126 命令找到了,但文件无法执行
127 命令找不到
>128 命令因收到信号而死亡

Shell 脚本可以使用exit 命令传递一个退出值给它的调用者。
只要将一个数字传递给它,作为第一个参数即可。脚本会立即退出,并且调用者会收到该数字且作为脚本的退出值:
exit 42

此命令还是很常见和使用的,往往使用在异常报错的退出或者传递成功的标志


6.2.2 if-elif-else-fi 语句

Shell 脚本中也非常常见的条件判断语句

使用程序的退出状态,最简单的方式就是使用if 语句。一般语法如下:

if pipeline
	[ pipeline ... ]
then
	statements-if-true-1
[ elif pipeline
	[ pipeline ... ]
  then
  	statements-if-true-2
][else 
	statements-if-all-else-fails]
fi

方括号表示的是可选的部分

Shell执行第一组介于if 与 then 之间的语句块。如果最后一条执行的语句成功退出,它便执行statements-if-true-1,否则,如果elif ,它会尝试下一组语句块。如果最后一条语句成功地退出,则会执行statements-if-true-2.它会以这种方式继续,执行相对应地语句块,直到它碰到一个成功退出地命令为止。

如果if 或 elif 语句里没有一个为真,并且else 子句存在,它会执行statements-if-all-else-fails.否则,它什么事也不做。整个if …fi 语句地退出状态,就是在then 或else 后面地最后一个被执行命令地退出状态。如果无任何命令执行,则退出状态为0.

说这么多废话,就是和其他语言的 if --else if --else 类似,其中fi (是if 反过来)作为结束标志

if [ $a -eq $b ]
then
	echo "a 等于 b"
	exit 0
elif [ $a -gt $b ]
then
	echo "a 大于 b"
else
	echo "a 小于 b"

echo "两个数对比成功"

#如果 a=10,b=20 则输出 a小于b  两个数对比成功
#如果 a=10,b=10 则输出 a 等于 b (exit 直接终止程序了,后面不在输出)
#如果 a=20.b=10 则输出 a大于b 两个数对比成功

补充关系运算符

运算符 说明
-eq 检测两个数是否相等,相等返回true
-ne 检测两个数是否不相等,不相等返回true
-gt 检测左边数是否大于右边,是返回true
-lt 检测左边数是否小于右边,是返回true
-ge 检测左边数是否大于等于右边,是返回true
-le 检测左边数是否小于等于右边,是返回true

6.2.4 test命令

test 命令 用途:为了测试Shell 脚本里的条件,通过退出状态返回其结果。要特别注意的是:这个命令的第二种形式,方括号根据字面意义逐字地输入,且必须与括起来的expression 以空白隔开

###test 语法   
## test [ expression ]   或  [ [expression ] ]
#第一种
if test "$str1" = "str2"
then
	...
fi


#第二种
if [ "$str1" = "$str2" ]
then
	...
fi

这个命令 经常犯的错 就在 空格,有时忘记打了,导致判断语句不生效,最后白白浪费时间去排查

test 表达式
这里罗列写常用的判断运算符,6.2.2章节中的补充运算符也是

运算符 如果…则为真
= 等于则为真 *常用,这里和java 双等号 不一样,注意点
!= 不相等则为真 *常用
-z 字符串 字符串的长度为零则为真
-n 字符串 字符串的长度不为零则为真
-e 文件名 如果文件存在则为真 *常用
-r 文件名 如果文件存在且可读则为真
-w 文件名 如果文件存在且可写则为真
-x 文件名 如果文件存在且可执行则为真
-s 文件名 如果文件存在且至少有一个字符则为真
-d 文件名 如果文件存在且为目录则为真
-f 文件名 如果文件存在且为普通文件则为真 *常用
-c 文件名 如果文件存在且为字符型特殊文件则为真
-b 文件名 如果文件存在且为块特殊文件则为真
#也可以测试否定的结果,只需前置!字符即可。
if[ -f "$file" ]
then 
	echo $file is a regular file
elif [ -d "$file" ]
then
	echo $file is a directory
fi

if [ ! -x "$file" ]
then
	echo $file is NOT executable
fi

test 的注意点
1.需要参数:

所有Shell 变量展开都应该以引号括起来  
if[ -f "$file" ],不加引号的话,万一$file是空的,就会报错了

2.字符串 特殊处理

字符串值为空 或是开头带有一个减号时,test会被混淆。因此会有种写法:
在字符串值前置字母X(X的使用时随意的,但这是传统用法)
if [ "X$answer" = "Xyes" ]

说实话,本人在工作中,遇到过此类情况,看到别人这么写,当时是满头问号

3.test 是可以被愚弄的
用起来难度较高,这里不多说

4.只能作整数数字测试
这个不多说,别判断浮点数,别用

6.3 case 语句

不多说Shell 的case …esac 为多选择语句,与其他语言中的swich…case类似
每个case 分支用右圆括号开始,用两个分号;;表示 break,即执行结束,跳出整个case…esac语句,esac(就是case反过来)作为结束标志。

casein
模式1)                    ##第一种情况
	command1
	command2
	...
	;;
模式2)					 ##第二种情况
	command1
	command2
	...
	;;
*)						##如果无匹配,则是其他,相当于defalt
	command1
	;;
esac

#示例1:
case $1 in
-f)
	....              #针对-f选项的程序代码
	;;
-d | --directory)     #允许长选项
	...				  #针对-d选项的程序代码
	;;
*)
	echo $1:unknown option >&2   #(>&2,是传送输出到标准错误)
	exit 1
	;;
esac

#示例2:
echo '输入1到4之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
1) echo '你选择了1'
;;
2) echo '你选择了2'
;;
3) echo '你选择了3'
;;
4) echo '你选择了4'
;;
*) echo '你没有选择输入1到4之间的数字'
;;
esac

6.4 循环

6.4.1 for 循环

for 循环里的in 列表是可选的,如果省略,Shell 循环会遍历整个命令行参数

for var in item1 item2 ... itemN
do
	command1
	command2
	...
	commandN
done

#示例1:
for loop in 1 2 3 4 5
do
    echo "The value is: $loop"
done
#输出
The value is: 1
The value is: 2
The value is: 3
The value is: 4
The value is: 5

#示例2:
for str in This is a string
do
    echo $str
done

#输出
This
is
a
string

6.4.2 while 与 until循环

与程序语言类似
while 和 until 唯一的不同之处在于,如何退出condition 的退出状态。只要condition 是成功退出,while 会继续循环。只要condition 未成功结束,until 则执行循环。
until 用的少

语法如下

while conditon
do 
	statements
done

until conditon
do 
	statements
done

#示例:
int=1
while(($int<=5 ))
do
	echo $int 
	let "int++"
done
#let 命令是 BASH 中用于计算的工具,用于执行一个或多个表达式,
#变量计算中不需要加上$ 来表示变量。
#如果表达式中包含了空格或其他特殊字符,则必须引起来
#输出:
1
2
3
4
5

#无限循环
while :
do 
	command
done
#或者
while true
do
	command
done
#或者
for (( ; ;))

6.4.3 break 与 continue

break 与 continue 都接受可选的数值参数,可分别用来指出要中断或继续

while con1			#外部循环
do
	while con2      #内部循环
	do
		...
		break 2     #外部循环的中断
	done
done
...					#在中断之后,继续执行这里的程序




while :
do
    echo -n "输入 1 到 5 之间的数字: "
    read aNum
    case $aNum in
        1|2|3|4|5) echo "你输入的数字为 $aNum!"
        ;;
        *) echo "你输入的数字不是 1 到 5 之间的!"
            continue
            echo "游戏结束"
        ;;
    esac
done

#当输入大于5的数字时,该例中的循环不会结束,语句 echo "游戏结束" 永远不会被执行。

6.4.4 shift与选项的处理

在执行shift 之后,原来的$1 就会消失,以$2 的旧值取代,$2的新值即为 $ 3的旧值,以此类推,而$ #的值就会逐次减少
位置参数可以用shift命令左移。比如shift 3表示原来的$4现在变成$1,原来的$5现在变成$2等等,原来的$1、$2、$3丢弃,$0不移动。不带参数的shift命令相当于shift 1。

6.5 函数

[ function ] funname [()]

{

    action;

    [return int;]

}
#示例1
demoFun(){
    echo "这是我的第一个 shell 函数!"
}
echo "-----函数开始执行-----"
demoFun
echo "-----函数执行完毕-----"

#输出:
-----函数开始执行-----
这是我的第一个 shell 函数!
-----函数执行完毕-----


#示例2
funWithReturn(){
    echo "这个函数会对输入的两个数字进行相加运算..."
    echo "输入第一个数字: "
    read aNum
    echo "输入第二个数字: "
    read anotherNum
    echo "两个数字分别为 $aNum$anotherNum !"
    return $(($aNum+$anotherNum))
}
funWithReturn
echo "输入的两个数字之和为 $? !"

#输出:
这个函数会对输入的两个数字进行相加运算...
输入第一个数字: 
1
输入第二个数字: 
2
两个数字分别为 1 和 2 !
输入的两个数字之和为 3 !

#在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数...

#带参数的函数示例3:
funWithParam(){
    echo "第一个参数为 $1 !"
    echo "第二个参数为 $2 !"
    echo "第十个参数为 $10 !"
    echo "第十个参数为 ${10} !"
    echo "第十一个参数为 ${11} !"
    echo "参数总数有 $# 个!"
    echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73

#输出:
第一个参数为 1 !
第二个参数为 2 !
第十个参数为 10 !
第十个参数为 34 !
第十一个参数为 73 !
参数总数有 11 个!
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !

1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255
3、注意 在Shell 函数体里使用exit 会终止整个Shell 函数
4、注意, 10 不 能 获 取 第 十 个 参 数 , 获 取 第 十 个 参 数 需 要 10 不能获取第十个参数,获取第十个参数需要 10{10}。当n>=10时,需要使用${n}来获取参数。

参数处理 说明
$# 传递到脚本或函数的参数个数
$* 以一个单字符串显示所有向脚本传递的参数
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的ID号
$@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。
$- 显示Shell使用的当前选项,与set命令功能相同。
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

七 输入/输出、文件与命令执行

7.1 标准输入、标准输出与标准错误输出

标准输入---->数据来源
标准输出---->数据出口
标准错误输出---->报告问题的地方

命令 说明
command > file 将输出重定向到 file。
command < file 将输入重定向到 file。
command >> file 将输出以追加的方式重定向到 file。
n > file 将文件描述符为 n 的文件重定向到 file。
n >> file 将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m 将输出重定向到 file。
n <& m 将输入文件 m 和 n 合并。
<< tag 将开始标记 tag 和结束标记 tag 之间的内容作为输入。

需要注意:
文件描述符

  • 0 通常是标准输入(STDIN)
  • 1 是标准输出(STDOUT)
  • 2 是标准错误输出(STDERR)

输出重定向

command1 > file1

上面这个命令执行command1然后将输出的内容存入file1。
注意任何file1内的已经存在的内容将被新内容替代。如果要将新内容添加在文件末尾,请使用>>操作符。

#执行下面的 who 命令,它将命令的完整的输出重定向在用户文件中(users):
$ who > users

#执行后,并没有在终端输出信息,这是因为输出已被从默认的标准输出设备(终端)重定向到指定的文件。
#你可以使用 cat 命令查看文件内容:

$ cat users

#输出:
_mbsetupuser console  Oct 31 17:35 
tianqixin    console  Oct 31 17:35 
tianqixin    ttys000  Dec  1 11:33 

#输出重定向会覆盖文件内容,请看下面的例子:
$ echo "菜鸟教程:www.runoob.com" > users
$ cat users

#输出:
菜鸟教程:www.runoob.com

#如果不希望文件内容被覆盖,可以使用 >> 追加到文件末尾,例如:
$ echo "菜鸟教程:www.runoob.com" >> users
$ cat users

#输出:
菜鸟教程:www.runoob.com
菜鸟教程:www.runoob.com

输入出重定向

#本来需要从键盘获取输入的命令会转移到文件读取内容
command1 < file1

示例:我们需要统计 users 文件的行数,执行以下命令

$ wc -l users
       2 users

$  wc -l < users
       2 

#注意:上面两个例子的结果不同:第一个例子,会输出文件名;第二个不会,因为它仅仅知道从标准输入读取内容。


command1 < infile > outfile
#同时替换输入和输出,执行command1,从文件infile读取内容,然后将输出写入到outfile中。

重定向深入讲解

标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。

默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。

#如果希望 stderr 重定向到 file,可以这样写:
$ command 2>file

#如果希望 stderr 追加到 file 文件末尾,可以这样写:
$ command 2>>file

#如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:
$ command > file 2>&1

或者

$ command >> file 2>&1

#如果希望对 stdin 和 stdout 都重定向,可以这样写
$ command < file1 >file2
#command 命令将 stdin 重定向到 file1,将 stdout 重定向到 file2。

Here Document
Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。

#基本形式
command << delimiter
    document
delimiter

#它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。
#结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。
#开始的delimiter前后的空格会被忽略掉。

#示例 在命令行中通过 wc -l 命令计算 Here Document 的行数
$ wc -l << EOF
    欢迎来到
    菜鸟教程
    www.runoob.com
EOF
3          # 输出结果为 3 行

cat << EOF
欢迎来到
菜鸟教程
www.runoob.com
EOF


#输出:
欢迎来到
菜鸟教程
www.runoob.com

实战
这个是在实际项目中经常用到的

#如果希望屏蔽 stdout 和 stderr,可以这样写:
command > /dev/null 2>&1 

#部署时 在Linux 上静默启动weblogic 
nohup startWeblogic.sh >/dev/null 2>&1 & 
#这里的 2 和 > 之间不可以有空格,2> 是一体的时候才表示错误输出

7.2 使用 read 读取行

read [ -r ] variable
用途:将信息读入一个或多个Shell变量
主要选项:-r 原始读取

未完待续

总结


提示:这里对Shell 的一些知识点 做了笔记,想要好好理理Shell 知识点的不妨 慢慢看。对于我来说 意义更在于 以前经常使用的命令和写法等,只是一知半解,整理过后,已经解答我很多一部分疑问了。

你可能感兴趣的:(Linux,shell)