linux之shell编程入门教程

linux之shell编程入门教程

Posted by yishuifengxiao on 2019-06-13

1 Shell 概述

1)需要看懂运维人员编写的 Shell 程序。

2)偶尔会编写一些简单 Shell 程序来管理集群、提高开发效率。

shell 在线工具(w3c 菜鸟提供)

2 Shell 解析器

2.1 Shell 解析器

(1)Linux 提供的 Shell 解析器有:

1
2
3
4
5
6
7
[root@localhost ~]$ cat /etc/shells
/bin/sh
/bin/bash
/sbin/nologin
/bin/dash
/bin/tcsh
/bin/csh

sh/bash/csh/Tcsh/ksh/pdksh 等 shell 的区别

  • sh(全称 Bourne Shell): 是 UNIX 最初使用的 shell,而且在每种 UNIX 上都可以使用。
  • Bourne Shell 在 shell 编程方面相当优秀,但在处理与用户的交互方面做得不如其他几种 shell。
  • bash(全称 Bourne Again Shell): LinuxOS 默认的,它是 Bourne Shell 的扩展。 与 Bourne Shell 完全兼容,并且在 Bourne Shell 的基础上增加了很多特性。可以提供命令补全,命令编辑和命令历史等功能。它还包含了很多 C Shell 和 Korn Shell 中的优点,有灵活和强大的编辑接口,同时又很友好的用户界面。
  • csh(全称 C Shell): 是一种比 Bourne Shell 更适合的变种 Shell,它的语法与 C 语言很相似。
  • Tcsh: 是 Linux 提供的 C Shell 的一个扩展版本。
  • Tcsh 包括命令行编辑,可编程单词补全,拼写校正,历史命令替换,作业控制和类似 C 语言的语法,他不仅和 Bash Shell 提示符兼容,而且还提供比 Bash Shell 更多的提示符参数。
  • ksh (全称 Korn Shell): 集合了 C Shell 和 Bourne Shell 的优点并且和 Bourne Shell 完全兼容。
  • pdksh: 是 Linux 系统提供的 ksh 的扩展。
  • pdksh 支持人物控制,可以在命令行上挂起,后台执行,唤醒或终止程序。

2.2 bash 和 sh 的关系

1
2
3
[root@localhost bin]$ ll | grep bash
-rwxr-xr-x. 1 root root 941880 5月 11 2016 bash
lrwxrwxrwx. 1 root root 4 5月 27 2017 sh -> bash

2.3 默认解析器

Centos 默认的解析器是 bash

1
2
[root@localhost bin]$ echo $SHELL
/bin/bash

3 Shell 脚本入门

3.1 脚本格式

脚本以 #!/bin/bash 开头(指定解析器)

当我们运行一个程序时,有时候会报错

/bin/bash^M: 坏的解释器: 没有那个文件或目录

原因在于是因为该文件在 windows 系统上打开过,关闭后其中的空格符号和 Linux 的不同,导致这个报错,我们可以通过 sed 命令与正则的配合将文件中的空格符号替换成 linux 的空格。

解决办法

1
2
#需要运行脚本全称为    test.sh
sed -i 's/\r$//' test.sh

3.2 单命令行脚本

  1. 需求:创建一个 Shell 脚本,输出 helloworld

  2. 案例实操:

1
2
[root@localhost datas]$ touch helloworld.sh
[root@localhost datas]$ vi helloworld.sh

在 helloworld.sh 中输入如下内容

1
2
#!/bin/bash
echo "helloworld"

3.3 脚本的常用执行方式

3.3.1 bash 或 sh+方式执行

采用 bash 或 sh+脚本的相对路径或绝对路径(不用赋予脚本+x 权限)

sh+脚本的相对路径

1
[root@localhost datas]$ sh helloworld.sh

输出结果为:

Helloworld

sh+脚本的绝对路径

1
[root@localhost datas]$ sh /home/root/datas/helloworld.sh

输出结果为:

helloworld

bash+脚本的相对路径

1
[root@localhost datas]$ bash helloworld.sh

输出结果为:

Helloworld

bash+脚本的绝对路径

1
[root@localhost datas]$ bash /home/root/datas/helloworld.sh

输出结果为:

Helloworld

3.3.2 脚本路径执行

采用输入脚本的绝对路径或相对路径执行脚本(必须具有可执行权限+x)

  1. 首先要赋予 helloworld.sh 脚本的+x 权限
1
[root@localhost datas]$ chmod 777 helloworld.sh
  1. 执行脚本
1
2
3
4
5
6
7
相对路径
[root@localhost datas]$ ./helloworld.sh
Helloworld

绝对路径
[root@localhost datas]$ /home/root/datas/helloworld.sh
Helloworld

注意:第一种执行方法,本质是 bash 解析器帮你执行脚本,所以脚本本身不需要执行权限。第二种执行方法,本质是脚本需要自己执行,所以需要执行权限。

3.3 多命令行脚本

  1. 需求:

在/home/root/目录下创建一个 banzhang.txt,在 banzhang.txt 文件中增加“I love cls”。

  1. 案例实操:
1
2
[root@localhost datas]$ touch batch.sh
[root@localhost datas]$ vi batch.sh

在 batch.sh 中输入如下内容

1
2
3
4
5
#!/bin/bash

cd /home/root
touch cls.txt
echo "I love cls" >>cls.txt

3.4 Shell 输入/输出重定向

大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回 ​​ 到您的终端。一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端。

重定向命令列表如下:

1
2
3
4
5
6
7
8
command > file	将输出重定向到 file。
command < file 将输入重定向到 file。
command >> file 将输出以追加的方式重定向到 file。
n > file 将文件描述符为 n 的文件重定向到 file。
n >> file 将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m 将输出文件 m 和 n 合并。
n <& m 将输入文件 m 和 n 合并。
<< tag 将开始标记 tag 和结束标记 tag 之间的内容作为输入

需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

具体方法参见 https://www.runoob.com/linux/linux-shell-io-redirections.html

输出重定向

重定向一般通过在命令间插入特定的符号来实现。特别的,这些符号的语法如下所示:

1
2
command1 > file1
上面这个命令执行command1然后将输出的内容存入file1。

注意任何 file1 内的已经存在的内容将被新内容替代。如果要将新内容添加在文件末尾,请使用>>操作符。

实例

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

1
$ who > users

执行后,并没有在终端输出信息,这是因为输出已被从默认的标准输出设备(终端)重定向到指定的文件。

你可以使用 cat 命令查看文件内容:

1
2
3
4
$ cat users
_mbsetupuser console Oct 31 17:35
tianqixin console Oct 31 17:35
tianqixin ttys000 Dec 1 11:33

输出重定向会覆盖文件内容,请看下面的例子:

1
2
3
4
$ echo "教程:www.demo.com" > users
$ cat users
教程:www.demo.com
$

如果不希望文件内容被覆盖,可以使用 >> 追加到文件末尾,例如:

1
2
3
4
5
$ echo "教程:www.demo.com" >> users
$ cat users
教程:www.demo.com
教程:www.demo.com
$

3.4.1 /dev/null 文件

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:

1
$ command > /dev/null

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出”的效果。

如果希望屏蔽 stdout 和 stderr,可以这样写:

1
$ command > /dev/null 2>&1

注意:0 是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

3.4.2 &说明

1
2
$ command > file 2>&1
$ command >> file 2>&1

这里的&没有固定的意思

放在>后面的&,表示重定向的目标不是一个文件,而是一个文件描述符,内置的文件描述符如下

1
2
3
1 => stdout
2 => stderr
0 => stdin

换言之 2>1 代表将 stderr 重定向到当前路径下文件名为 1 的 regular file 中,而 2>&1 代表将 stderr 重定向到文件描述符为 1 的文件(即/dev/stdout)中,这个文件就是 stdout 在 file system 中的映射

而&>file 是一种特殊的用法,也可以写成>&file,二者的意思完全相同,都等价于

1
>file 2>&1

此处&>或者>&视作整体,分开没有单独的含义

顺序问题

1
2
3
find /etc -name .bashrc > list 2>&1
# 我想问为什么不能调下顺序,比如这样
find /etc -name .bashrc 2>&1 > list

这个是从左到右有顺序的

第一种

1
xxx > list 2>&1

先将要输出到 stdout 的内容重定向到文件,此时文件 list 就是这个程序的 stdout,再将 stderr 重定向到 stdout,也就是文件 list

第二种

1
xxx 2>&1 > list

先将要输出到 stderr 的内容重定向到 stdout,此时会产生一个 stdout 的拷贝,作为程序的 stderr,而程序原本要输出到 stdout 的内容,依然是对接在 stdout 原身上的,因此第二步重定向 stdout,对 stdout 的拷贝不产生任何影响

特别注意

对于上面 ‘2>&1’,举个例子,比如说:

1
$ find /etc -names "*.txt"  >list 2>&1

从左往右执行,执行到 >list,此时的 stdout 为 list;而执行到 2>&1,表示 stderr 重定向到 stdout,这里也就是 list 文件。

因为 [ find /etc -names “*.txt” ] 这条命令是错误的( -names 应该是 -name)。

本来要输出到终端屏幕的错误信息:

1
find: unknown predicate `-names`

被重定向到了 stdout 也就是 list 文件中,所以屏幕不会出现错误信息,而是打印到了 list 文件中。

cat list 可以查看到 find: unknown predicate `-names’ 就在里面。

4 Shell 中的变量

4.1 系统变量

4.1.1. 常用系统变量

$HOME、$PWD、$SHELL、$USER 等

4.1.2 案例实操

(1)查看系统变量的值

1
2
[root@localhost datas]$ echo $HOME
/home/root

(2)显示当前 Shell 中所有变量:set

1
2
3
4
5
[root@localhost datas]$ set
BASH=/bin/bash
BASH_ALIASES=()
BASH_ARGC=()
BASH_ARGV=()

4.1.3 Shell 字符串

字符串是 shell 编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号,也可以不用引号。单双引号的区别跟 PHP 类似。

单引号

1
str='this is a string'

单引号字符串的限制:

单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。

双引号

1
2
3
4
your_name='demo'
str="Hello, I know you are \"$your_name\"! \n"

echo -e $str

输出结果为:

Hello, I know you are “demo”!

双引号的优点:

  • 双引号里可以有变量
  • 双引号里可以出现转义字符

拼接字符串

1
2
3
4
5
6
7
8
9
your_name="demo"
# 使用双引号拼接
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1
# 使用单引号拼接
greeting_2='hello, '$your_name' !'
greeting_3='hello, ${your_name} !'
echo $greeting_2 $greeting_3

输出结果为:

1
2
hello, demo ! hello, demo !
hello, demo ! hello, ${your_name} !

获取字符串长度

1
2
string="abcd"
echo ${#string} #输出 4

提取子字符串

以下实例从字符串第 2 个字符开始截取 4 个字符:

1
2
string="demo is a great site"
echo ${string:1:4} # 输出 unoo

查找子字符串

查找字符 i 或 o 的位置(哪个字母先出现就计算哪个):

1
2
string="demo is a great site"
echo `expr index "$string" io` # 输出 4

注意:

以上脚本中 ` 是反引号,而不是单引号 ‘

4.1.4 字符串截取

假设有变量 var=http://www.aaa.com/123.htm

  1. # 号截取,删除左边字符,保留右边字符。
1
2
echo ${var#*//}
其中 var 是变量名,# 号是运算符,*// 表示从左边开始删除第一个 // 号及左边的所有字符

即删除 http:// , 结果是 :www.aaa.com/123.htm

  1. ## 号截取,删除左边字符,保留右边字符。
1
2
echo ${var##*/}
##*/ 表示从左边开始删除最后(最右边)一个 / 号及左边的所有字符

即删除 http://www.aaa.com/ , 结果是 123.htm

  1. %号截取,删除右边字符,保留左边字符
1
2
echo ${var%/*}
%/* 表示从右边开始,删除第一个 / 号及右边的字符

结果是:http://www.aaa.com

  1. %% 号截取,删除右边字符,保留左边字符
1
2
echo ${var%%/*}
%%/* 表示从右边开始,删除最后(最左边)一个 / 号及右边的字符

结果是:http:

  1. 从左边第几个字符开始,及字符的个数
1
2
echo ${var:0:5}
其中的 0 表示左边第一个字符开始,5 表示字符的总个数。

结果是:http:

  1. 从左边第几个字符开始,一直到结束。
1
2
echo ${var:7}
其中的 7 表示左边第8个字符开始,一直到结束。

结果是 :www.aaa.com/123.htm

  1. 从右边第几个字符开始,及字符的个数
1
2
echo ${var:0-7:3}
其中的 0-7 表示右边算起第七个字符开始,3 表示字符的个数。

结果是:123

  1. 从右边第几个字符开始,一直到结束。
1
2
echo ${var:0-7}
表示从右边第七个字符开始,一直到结束。

结果是:123.htm

注:(左边的第一个字符是用 0 表示,右边的第一个字符用 0-1 表示)

总结

、## 表示从左边开始删除。

  • 一个 # 表示从左边删除到第一个指定的字符;
  • 两个 # 表示从左边删除到最后一个指定的字符。

%、%% 表示从右边开始删除。

  • 一个 % 表示从右边删除到第一个指定的字符;
  • 两个 % 表示从左边删除到最后一个指定的字符。

删除包括了指定的字符本身。

示例

1
2
3
4
5
6
7
8
9
10
#!bin/bash
#author:amau

var="http://www.demo.com/linux/linux-shell-variable.html"

s1=${var%%t*}
s2=${var%t*}
s3=${var%%.*}
s4=${var#*/}
s5=${var##*/}
  • echo “关于字符串的截取%,#的使用方法” echo “
  • 原字符串为:”${var} echo “
  • %%t*的效果:”${s1} echo “
  • %t*的效果:”${s2} echo “
  • %%.*的效果:”${s3} echo “
  • */的效果:”${s4} echo “

  • */的效果:”${s5}

运行结果:

1
2
3
4
5
6
7
关于字符串的截取%,#的使用方法
原字符串为:http://www.demo.com/linux/linux-shell-variable.html
%%t*的效果:h
%t*的效果:http://www.demo.com/linux/linux-shell-variable.h
%%.*的效果:http://www
#*/的效果:/www.demo.com/linux/linux-shell-variable.html
##*/的效果:linux-shell-variable.html

4.2 自定义变量

4.2.1 基本语法

  • 定义变量:变量=值
  • 撤销变量:unset 变量
  • 声明静态变量:readonly 变量,注意:不能 unset
参数处理 说明
$# 传递到脚本的参数个数
$* 以一个单字符串显示所有向脚本传递的参数。如”$*”用「”」括起来的情况、以”$1 $2 … $n”的形式输出所有参数
$$ 脚本运行的当前进程 ID 号
$! 后台运行的最后一个进程的 ID 号
$$ 脚本运行的当前进程 ID 号
$@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。如”$@”用「”」括起来的情况、以”$1” “$2” … “$n” 的形式输出所有参数。
$- 显示 Shell 使用的当前选项,与 set 命令功能相同。
$? 显示最后命令的退出状态。0 表示没有错误,其他任何值表明有错误

4.2.2 变量定义规则

  • 变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写。
  • 等号两侧不能有空格
  • 在 bash 中,变量默认类型都是字符串类型,无法直接进行数值运算。
  • 变量的值如果有空格,需要使用双引号或单引号括起来。
  • 不能使用 bash 里的关键字(可用 help 命令查看保留关键字)

案例实操

(1)定义变量 A

1
2
[root@localhost datas]$ A=5
[root@localhost datas]$ echo $A

(2)给变量 A 重新赋值

1
2
[root@localhost datas]$ A=8
[root@localhost datas]$ echo $A

(3)撤销变量 A

1
2
[root@localhost datas]$ unset A
[root@localhost datas]$ echo $A

(4) s 声明静态变量

声明静态的变量 B=2,不能 unset

1
2
3
4
5
[root@localhost datas]$ readonly B=2
[root@localhost datas]$ echo $B

[root@localhost datas]$ B=9
-bash: B: readonly variable

(5)变量默认类型

在 bash 中,变量默认类型都是字符串类型,无法直接进行数值运算

1
2
[root@localhost ~]$ C=1+2
[root@localhost ~]$ echo $C

输出结果为

1+2

(6)变量的值有空格

变量的值如果有空格,需要使用双引号或单引号括起来

1
2
3
4
[root@localhost ~]$ D=I love banzhang
-bash: world: command not found
[root@localhost ~]$ D="I love banzhang"
[root@localhost ~]$ echo $A

输出结果为

I love banzhang

(7)变量提升为全局环境变量

可把变量提升为全局环境变量,可供其他 Shell 程序使用

export 变量名

1
[root@localhost datas]$ vim helloworld.sh

在 helloworld.sh 文件中增加 echo $B

1
2
3
4
5
6
#!/bin/bash

echo "helloworld"
echo $B

[root@localhost datas]$ ./helloworld.sh

输出结果为

Helloworld

发现并没有打印输出变量 B 的值。

1
2
[root@localhost datas]$ export B
[root@localhost datas]$ ./helloworld.sh

输出结果为

helloworld
2

除了显式地直接赋值,还可以用语句给变量赋值

1
2
3
for file in `ls /etc`

for file in $(ls /etc)

以上语句将 /etc 下目录的文件名循环出来。

4.3 特殊变量:$n

基本语法

1
$n

功能描述:n 为数字,$0代表该脚本名称,$1-$9代表第一到第九个参数,十以上的参数,十以上的参数需要用大括号包含,如${10})

案例实操 : 输出该脚本文件名称、输入参数 1 和输入参数 2 的值

1
2
3
4
5
6
7
8
9
#!/bin/bash
# author:教程
# url:www.demo.com

echo "Shell 传递参数实例!";
echo "第一个参数为:$1";

echo "参数个数为:$#";
echo "传递的参数作为一个字符串显示:$*";

执行脚本,输出结果如下所示:

1
2
3
4
5
6
$ chmod +x test.sh
$ ./test.sh 1 2 3
Shell 传递参数实例!
第一个参数为:1
参数个数为:3
传递的参数作为一个字符串显示:1 2 3

4.4 特殊变量:$

基本语法

1
$#

(功能描述:获取所有输入参数个数,常用于循环)

案例实操 : 获取输入参数的个数

1
2
3
4
5
6
7
8
9
10
11
[root@localhost datas]$ vim parameter.sh

#!/bin/bash
echo "$0 $1 $2"
echo $#

[root@localhost datas]$ chmod 777 parameter.sh

[root@localhost datas]$ ./parameter.sh cls xz
parameter.sh cls xz
2

4.5 特殊变量:$*、$@

基本语法

1
2
3
$*	(功能描述:这个变量代表命令行中所有的参数,$*把所有的参数看成一个整体)

$@ (功能描述:这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待)

案例实操 : 打印输入的所有参数

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost datas]$ vim parameter.sh

#!/bin/bash
echo "$0 $1 $2"
echo $#
echo $*
echo $@

[root@localhost datas]$ bash parameter.sh 1 2 3
parameter.sh 1 2
3
1 2 3
1 2 3

4.6 特殊变量:$?

基本语法

$? (功能描述:最后一次执行的命令的返回状态。如果这个变量的值为 0,证明上一个命令正确执行;如果这个变量的值为非 0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。)

案例实操 : 判断 helloworld.sh 脚本是否正确执行

1
2
3
4
[root@localhost datas]$ ./helloworld.sh
hello world
[root@localhost datas]$ echo $?
0

4.7 参数传递

在为 shell 脚本传递的参数中如果包含空格,应该使用单引号或者双引号将该参数括起来,以便于脚本将这个参数作为整体来接收。

在有参数时,可以使用对参数进行校验的方式处理以减少错误发生

1
2
3
4
5
if [ -n "$1" ]; then
echo "包含第一个参数"
else
echo "没有包含第一参数"
fi

注意

1
2
3
中括号 [] 与其中间的代码应该有空格隔开

[ -n STRING ] or [ STRING ] “STRING” 的长度为非零 non-zero则为真。

shell 里面的中括号(包括单中括号与双中括号)可用于一些条件的测试

  • 算术比较, 比如一个变量是否为 0, [ $var -eq 0 ]。
  • 文件属性测试,比如一个文件是否存在,[ -e $var ], 是否是目录,[ -d $var ]。
  • 字符串比较, 比如两个字符串是否相同, [[ $var1 = $var2 ]]。
    -

5 运算符

5.1 基本语法

(1)“$((运算式))”或“$[运算式]”

(2)expr + , - , *, /, % 加,减,乘,除,取余

注意:expr 运算符间要有空格

5.2 案例实操

(1)计算 3+2 的值

1
2
[root@localhost datas]$ expr 2 + 3
5

(2)计算 3-2 的值

1
2
[root@localhost datas]$ expr 3 - 2
1

(3)计算(2+3)X4 的值

(a)expr 一步完成计算

1
2
[root@localhost datas]$ expr `expr 2 + 3` \* 4
20

(b)采用$[运算式]方式

1
2
[root@localhost datas]# S=$[(2+3)*4]
[root@localhost datas]# echo $S

注意

使用 expr 命令时,表达式中的运算符左右必须包含空格,如果不包含空格,将会输出表达式本身

1
2
expr 5+6    // 直接输出 5+6
expr 5 + 6 // 输出 11

对于某些运算符,还需要我们使用符号”\”进行转义,否则就会提示语法错误。

1
2
expr 5 * 6       // 输出错误
expr 5 \* 6 // 输出30

6 条件判断

6.1 基本语法

[ condition ](注意 condition 前后要有空格)

注意:条件非空即为 true,[ root ]返回 true,[] 返回 false。

6.2 常用判断条件

(1)两个整数之间比较

= 字符串比较

-lt 小于(less than)

-le 小于等于(less equal)

-eq 等于(equal)

-gt 大于(greater than)

-ge 大于等于(greater equal)

-ne 不等于(Not equal)

(2)按照文件权限进行判断

-r 有读的权限(read)
-w 有写的权限(write)
-x 有执行的权限(execute)

(3)按照文件类型进行判断

-f 文件存在并且是一个常规的文件(file)
-e 文件存在(existence)
-d 文件存在并是一个目录(directory)

6.3 案例实操

(1)23 是否大于等于 22

1
2
3
[root@localhost datas]$ [ 23 -ge 22 ]
[root@localhost datas]$ echo $?
0

(2)helloworld.sh 是否具有写权限

1
2
3
[root@localhost datas]$ [ -w helloworld.sh ]
[root@localhost datas]$ echo $?
0

(3)/home/root/cls.txt 目录中的文件是否存在

1
2
3
[root@localhost datas]$ [ -e /home/root/cls.txt ]
[root@localhost datas]$ echo $?
1

(4)多条件判断

(&& 表示前一条命令执行成功时,才执行后一条命令,|| 表示上一条命令执行失败后,才执行下一条命令)

1
2
3
4
[root@localhost ~]$ [ condition ] && echo OK || echo notok
OK
[root@localhost datas]$ [ condition ] && [ ] || echo notok
notok

6.4 Shell 中判断语句 if 中-z 至-d 的意思

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
26
27
28
29
30
31
[ -a FILE ]  如果 FILE 存在则为真。
[ -b FILE ] 如果 FILE 存在且是一个块特殊文件则为真。
[ -c FILE ] 如果 FILE 存在且是一个字特殊文件则为真。
[ -d FILE ] 如果 FILE 存在且是一个目录则为真。
[ -e FILE ] 如果 FILE 存在则为真。
[ -f FILE ] 如果 FILE 存在且是一个普通文件则为真。
[ -g FILE ] 如果 FILE 存在且已经设置了SGID则为真。
[ -h FILE ] 如果 FILE 存在且是一个符号连接则为真。
[ -k FILE ] 如果 FILE 存在且已经设置了粘制位则为真。
[ -p FILE ] 如果 FILE 存在且是一个名字管道(F如果O)则为真。
[ -r FILE ] 如果 FILE 存在且是可读的则为真。
[ -s FILE ] 如果 FILE 存在且大小不为0则为真。
[ -t FD ] 如果文件描述符 FD 打开且指向一个终端则为真。
[ -u FILE ] 如果 FILE 存在且设置了SUID (set user ID)则为真。
[ -w FILE ] 如果 FILE 如果 FILE 存在且是可写的则为真。
[ -x FILE ] 如果 FILE 存在且是可执行的则为真。
[ -O FILE ] 如果 FILE 存在且属有效用户ID则为真。
[ -G FILE ] 如果 FILE 存在且属有效用户组则为真。
[ -L FILE ] 如果 FILE 存在且是一个符号连接则为真。
[ -N FILE ] 如果 FILE 存在 and has been mod如果ied since it was last read则为真。
[ -S FILE ] 如果 FILE 存在且是一个套接字则为真。
[ FILE1 -nt FILE2 ] 如果 FILE1 has been changed more recently than FILE2或FILE1 exists and FILE2 does not则为真。
[ FILE1 -ot FILE2 ] 如果 FILE1 比 FILE2 要老, 或者 FILE2 存在且 FILE1 不存在则为真。
[ FILE1 -ef FILE2 ] 如果 FILE1 和 FILE2 指向相同的设备和节点号则为真。
[ -o OPTIONNAME ] 如果 shell选项 “OPTIONNAME” 开启则为真。
[ -z STRING ] “STRING” 的长度为零则为真。
[ -n STRING ] or [ STRING ] “STRING” 的长度为非零 non-zero则为真。
[ STRING1 == STRING2 ] 如果2个字符串相同。 “=” may be used instead of “==” for strict POSIX compliance则为真。
[ STRING1 != STRING2 ] 如果字符串不相等则为真。
[ STRING1 < STRING2 ] 如果 “STRING1” sorts before “STRING2” lexicographically in the current locale则为真。
[ STRING1 > STRING2 ] 如果 “STRING1” sorts after “STRING2” lexicographically in the current locale则为真。

基本上和其他脚本语言一样。没有太大区别。不过值得注意的是。[]里面的条件判断。

1) 字符串判断

1
2
3
4
5
str1 = str2      当两个串有相同内容、长度时为真
str1 != str2      当串str1和str2不等时为真
-n str1        当串的长度大于0时为真(串非空)
-z str1        当串的长度为0时为真(空串)
str1         当串str1为非空时为真

2) 数字的判断

1
2
3
4
5
6
int1 -eq int2    两数相等为真
int1 -ne int2    两数不等为真
int1 -gt int2    int1大于int2为真
int1 -ge int2    int1大于等于int2为真
int1 -lt int2    int1小于int2为真
int1 -le int2    int1小于等于int2为真

3) 文件的判断

1
2
3
4
5
6
7
8
9
-r file     用户可读为真
-w file     用户可写为真
-x file     用户可执行为真
-f file     文件为正规文件为真
-d file     文件为目录为真
-c file     文件为字符特殊文件为真
-b file     文件为块特殊文件为真
-s file     文件大小非0时为真
-t file     当文件描述符(默认为1)指定的设备为终端时为真

4) 复杂逻辑判断

1
2
3
-a         与
-o        或
!        非

7 流程控制(重点)

7.1 if 判断

基本语法

1
2
3
4
5
6
7
8
9
10
if [ 条件判断式 ];then
程序
fi

或者

if [ 条件判断式 ]
then
程序
fi

注意事项

(1)[ 条件判断式 ],中括号和条件判断式之间必须有空格

(2)if 后要有空格

案例实操

(1)输入一个数字,如果是 1,则输出 banzhang zhen shuai,如果是 2,则输出 cls zhen mei,如果是其它,什么也不输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@localhost datas]$ touch if.sh
[root@localhost datas]$ vim if.sh

#!/bin/bash

if [ $1 -eq "1" ]
then
echo "banzhang zhen shuai"
elif [ $1 -eq "2" ]
then
echo "cls zhen mei"
fi

[root@localhost datas]$ chmod 777 if.sh
[root@localhost datas]$ ./if.sh 1
banzhang zhen shuai

7.2 case 语句

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
case $变量名 in
"值1")
如果变量的值等于值1,则执行程序1
;;
"值2")
如果变量的值等于值2,则执行程序2
;;
…省略其他分支…
*)
如果变量的值都不是以上的值,则执行此程序
;;
esac

注意事项

1)case 行尾必须为单词“in”,每一个模式匹配必须以右括号“)”结束。
2)双分号“;;”表示命令序列结束,相当于 java 中的 break。
3)最后的“*)”表示默认模式,相当于 java 中的 default。

案例实操

(1)输入一个数字,如果是 1,则输出 banzhang,如果是 2,则输出 cls,如果是其它,输出 renyao。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@localhost datas]$ touch case.sh
[root@localhost datas]$ vim case.sh

#!/bin/bash

case $1 in
"1")
echo "banzhang"
;;

"2")
echo "cls"
;;
*)
echo "renyao"
;;
esac

[root@localhost datas]$ chmod 777 case.sh
[root@localhost datas]$ ./case.sh 1
1

7.3 for 循环

7.3.1 基本语法 1

1
2
3
4
for (( 初始值;循环控制条件;变量变化 ))
do
程序
done

案例实操

(1)从 1 加到 100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@localhost datas]$ touch for1.sh
[root@localhost datas]$ vim for1.sh

#!/bin/bash

s=0
for((i=0;i<=100;i++))
do
s=$[$s+$i]
done
echo $s

[root@localhost datas]$ chmod 777 for1.sh
[root@localhost datas]$ ./for1.sh
“5050”

7.3.2 基本语法 2

1
2
3
4
for 变量 in 值1 值2 值3…
do
程序
done

案例实操

(1)打印所有输入参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@localhost datas]$ touch for2.sh
[root@localhost datas]$ vim for2.sh

#!/bin/bash
#打印数字



for i in $*
do
echo "ban zhang love $i "
done

[root@localhost datas]$ chmod 777 for2.sh
[root@localhost datas]$ bash for2.sh cls xz bd
ban zhang love cls
ban zhang love xz
ban zhang love bd

7.3.3 比较$*和$@区别

$*和$@都表示传递给函数或脚本的所有参数,不被双引号“”包含时,都以$1 $2 …$n 的形式输出所有参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@localhost datas]$ touch for.sh
[root@localhost datas]$ vim for.sh

#!/bin/bash

for i in $*
do
echo "ban zhang love $i "
done

for j in $@
do
echo "ban zhang love $j"
done

[root@localhost datas]$ bash for.sh cls xz bd
ban zhang love cls
ban zhang love xz
ban zhang love bd
ban zhang love cls
ban zhang love xz
ban zhang love bd


当它们被双引号“”包含时,“$*”会将所有的参数作为一个整体,以“$1 $2 …$n”的形式输出所有参数;“$@”会将各个参数分开,以“$1” “$2”…”$n”的形式输出所有参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@localhost datas]$ vim for.sh

#!/bin/bash

for i in "$*"
#$*中的所有参数看成是一个整体,所以这个for循环只会循环一次
do
echo "ban zhang love $i"
done

for j in "$@"
#$@中的每个参数都看成是独立的,所以“$@”中有几个参数,就会循环几次
do
echo "ban zhang love $j"
done

[root@localhost datas]$ chmod 777 for.sh
[root@localhost datas]$ bash for.sh cls xz bd
ban zhang love cls xz bd
ban zhang love cls
ban zhang love xz
ban zhang love bd

7.4 while 循环

基本语法

1
2
3
4
while [ 条件判断式 ]
do
程序
done

案例实操

(1)从 1 加到 100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost datas]$ touch while.sh
[root@localhost datas]$ vim while.sh

#!/bin/bash
s=0
i=1
while [ $i -le 100 ]
do
s=$[$s+$i]
i=$[$i+1]
done

echo $s

[root@localhost datas]$ chmod 777 while.sh
[root@localhost datas]$ ./while.sh
5050

8 read 读取控制台输入

基本语法

1
2
3
4
5
6
7
8
9
10
read(选项)(参数)

选项:
-p 指定读取值时的提示符;
-n 输入字符长度限制(达到6位,自动结束)
-t 指定读取值时等待的时间(秒)。
-s 隐藏输入内容

参数
变量:指定读取值的变量名

案例实操 : 提示 7 秒内,读取控制台输入的名称

1
2
3
4
5
6
7
8
9
10
11
[root@localhost datas]$ touch read.sh
[root@localhost datas]$ vim read.sh

#!/bin/bash

read -t 7 -p "Enter your name in 7 seconds " NAME
echo $NAME

[root@localhost datas]$ ./read.sh
Enter your name in 7 seconds xiaoze
xiaoze

以下实例读取键盘输入的内容并将其赋值给 shell 变量,为:-p 参数由于设置提示信息:

1
2
3
4
read -p "input a val:" a    #获取键盘输入的 a 变量数字
read -p "input b val:" b #获取键盘输入的 b 变量数字
r=$[a+b] #计算a+b的结果 赋值给r 不能有空格
echo "result = ${r}" #输出显示结果 r

测试结果:

1
2
3
input a val:1
input b val:2
result = 3

9 函数

9.1 系统函数

9.1.1 basename 基本语法

1
basename [string / pathname] [suffix]

(功能描述:basename 命令会删掉所有的前缀包括最后一个(‘/’)字符,然后将字符串显示出来。
选项:
suffix 为后缀,如果 suffix 被指定了,basename 会将 pathname 或 string 中的 suffix 去掉。

案例实操 : 截取该/home/root/banzhang.txt 路径的文件名称

1
2
3
4
[root@localhost datas]$ basename /home/root/banzhang.txt
banzhang.txt
[root@localhost datas]$ basename /home/root/banzhang.txt .txt
banzhang

9.1.2 dirname 基本语法

1
dirname 文件绝对路径

功能描述:从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下的路径(目录的部分))

案例实操 : 获取 banzhang.txt 文件的路径

1
2
[root@localhost ~]$ dirname /home/root/banzhang.txt
/home/root

9.2 自定义函数

基本语法

1
2
3
4
5
6
7
8
[ function ] funname[()]
{
Action;
[return int;]
}


funname

经验技巧

  1. 必须在调用函数地方之前,先声明函数,shell 脚本是逐行运行。不会像其它语言一样先编译。
  2. 函数返回值,只能通过$?系统变量获得,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。return 后跟数值 n(0-255)
  3. 可以带 function fun() 定义,也可以直接 fun() 定义,不带任何参数。
  4. 参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return 后跟数值 n(0-255

案例实操

(1)计算两个输入参数的和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@localhost datas]$ touch fun.sh
[root@localhost datas]$ vim fun.sh

#!/bin/bash
function sum()
{
s=0
s=$[ $1 + $2 ]
echo "$s"
}

read -p "Please input the number1: " n1;
read -p "Please input the number2: " n2;
sum $n1 $n2;

[root@localhost datas]$ chmod 777 fun.sh
[root@localhost datas]$ ./fun.sh
Please input the number1: 2
Please input the number2: 5
7

9.2.1 无参函数

下面的例子定义了一个函数并进行调用

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
# author:教程
# url:www.demo.com

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

输出结果:

1
2
3
-----函数开始执行-----
这是我的第一个 shell 函数!
-----函数执行完毕-----

9.2.2 带 return 函数

下面定义一个带有 return 语句的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
# author:教程
# url:www.demo.com

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

输出类似下面

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

函数返回值在调用该函数后通过 $? 来获得。

注意:所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 shell 解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。

9.2.3 函数参数

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

带参数的函数示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# author:教程
# url:www.demo.com

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
3
4
5
6
7
第一个参数为 1 !
第二个参数为 2 !
第十个参数为 10 !
第十个参数为 34 !
第十一个参数为 73 !
参数总数有 11 个!
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !

注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当 n>=10 时,需要使用${n}来获取参数。

10 Shell 工具(重点)

10.1 cut

cut 的工作就是“剪”,具体的说就是在文件中负责剪切数据用的。cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段输出。

10.1.1 基本用法

1
cut [选项参数]  filename

说明:默认分隔符是制表符

10.1.2 选项参数说明

选项参数 功能
-f 列号,提取第几列
-d 分隔符,按照指定分隔符分割列

10.1.3 案例实操

(0)数据准备

1
2
3
4
5
6
7
[root@localhost datas]$ touch cut.txt
[root@localhost datas]$ vim cut.txt
dong shen
guan zhen
wo wo
lai lai
le le

(1)切割 cut.txt 第一列

1
2
3
4
5
6
[root@localhost datas]$ cut -d " " -f 1 cut.txt
dong
guan
wo
lai
le

(2)切割 cut.txt 第二、三列

1
2
3
4
5
6
[root@localhost datas]$ cut -d " " -f 2,3 cut.txt
shen
zhen
wo
lai
le

(3)在 cut.txt 文件中切割出 guan

1
2
[root@localhost datas]$ cat cut.txt | grep "guan" | cut -d " " -f 1
guan

(4)选取系统 PATH 变量值,第 2 个“:”开始后的所有路径:

1
2
3
4
5
[root@localhost datas]$ echo $PATH
/usr/lib64/qt-3.3/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/root/bin

[root@localhost datas]$ echo $PATH | cut -d: -f 2-
/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/root/bin

(5)切割 ifconfig 后打印的 IP 地址

1
2
[root@localhost datas]$ ifconfig eth0 | grep "inet addr" | cut -d: -f 2 | cut -d" " -f1
192.168.1.102

10.2 sed

sed 是一种流编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”,接着用 sed 命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。

10.2.1 基本用法

1
sed [选项参数]  ‘command’  filename

选项参数说明

选项参数 功能
-e 直接在指令列模式上进行 sed 的动作编辑。

命令功能描述

1
2
3
a 	新增,a的后面可以接字串,在下一行出现
d 删除
s 查找并替换

10.2.2 案例实操

(0)数据准备

1
2
3
4
5
6
7
8
[root@localhost datas]$ touch sed.txt
[root@localhost datas]$ vim sed.txt
dong shen
guan zhen
wo wo
lai lai

le le

(1)将“mei nv”这个单词插入到 sed.txt 第二行下,打印。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@localhost datas]$ sed '2a mei nv' sed.txt
dong shen
guan zhen
mei nv
wo wo
lai lai

le le
[root@localhost datas]$ cat sed.txt
dong shen
guan zhen
wo wo
lai lai

le le

注意:文件并没有改变

(2)删除 sed.txt 文件所有包含 wo 的行

1
2
3
4
5
6
[root@localhost datas]$ sed '/wo/d' sed.txt
dong shen
guan zhen
lai lai

le le

(3)将 sed.txt 文件中 wo 替换为 ni

1
2
3
4
5
6
7
[root@localhost datas]$ sed 's/wo/ni/g' sed.txt
dong shen
guan zhen
ni ni
lai lai

le le

注意:‘g’表示 global,全部替换

(4)将 sed.txt 文件中的第二行删除并将 wo 替换为 ni

1
2
3
4
5
6
[root@localhost datas]$ sed -e '2d' -e 's/wo/ni/g' sed.txt
dong shen
ni ni
lai lai

le le

10.3 awk

一个强大的文本分析工具,把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理。

10.3.1 基本用法

1
2
3
awk [选项参数] ‘pattern1{action1}  pattern2{action2}...’ filename
pattern:表示AWK在数据中查找的内容,就是匹配模式
action:在找到匹配内容时所执行的一系列命令

选项参数说明

1
2
-F	指定输入文件折分隔符
-v 赋值一个用户定义变量

10.3.2 案例实操

  1. 数据准备
1
[root@localhost datas]$ sudo cp /etc/passwd ./
  1. 搜索 passwd 文件以 root 关键字开头的所有行,并输出该行的第 7 列。
1
2
[root@localhost datas]$ awk -F: '/^root/{print $7}' passwd
/bin/bash
  1. 搜索 passwd 文件以 root 关键字开头的所有行,并输出该行的第 1 列和第 7 列,中间以“,”号分割。
1
2
[root@localhost datas]$ awk -F: '/^root/{print $1","$7}' passwd
root,/bin/bash

注意:只有匹配了 pattern 的行才会执行 action

  1. 只显示/etc/passwd 的第一列和第七列,以逗号分割,且在所有行前面添加列名 user,shell 在最后一行添加”dahaige,/bin/zuishuai”。
1
2
3
4
5
6
7
[root@localhost datas]$ awk -F : 'BEGIN{print "user, shell"} {print $1","$7} END{print "dahaige,/bin/zuishuai"}' passwd
user, shell
root,/bin/bash
bin,/sbin/nologin
。。。
root,/bin/bash
dahaige,/bin/zuishuai

注意:BEGIN 在所有数据读取行之前执行;END 在所有数据执行之后执行。

  1. 将 passwd 文件中的用户 id 增加数值 1 并输出
1
2
3
4
5
[root@localhost datas]$ awk -v i=1 -F: '{print $3+i}' passwd
1
2
3
4

10.3.3 awk 的内置变量

1
2
3
FILENAME	文件名
NR 已读的记录数
NF 浏览记录的域的个数(切割后,列的个数)

10.3.4 案例实操

  1. 统计 passwd 文件名,每行的行号,每行的列数
1
2
3
4
[root@localhost datas]$ awk -F: '{print "filename:"  FILENAME ", linenumber:" NR  ",columns:" NF}' passwd
filename:passwd, linenumber:1,columns:7
filename:passwd, linenumber:2,columns:7
filename:passwd, linenumber:3,columns:7
  1. 切割 IP
1
2
[root@localhost datas]$ ifconfig eth0 | grep "inet addr" | awk -F: '{print $2}' | awk -F " " '{print $1}'
192.168.1.102
  1. 查询 sed.txt 中空行所在的行号
1
2
[root@localhost datas]$ awk '/^$/{print NR}' sed.txt
5

10.4 sort

sort 命令是在 Linux 里非常有用,它将文件进行排序,并将排序结果标准输出。

10.4.1 基本语法

1
2
3
4
5
6
7
sort(选项)(参数)


-n 依照数值的大小排序
-r 以相反的顺序来排序
-t 设置排序时所用的分隔字符
-k 指定需要排序的列

参数:指定待排序的文件列表

10.4.2 案例实操

  1. 数据准备
1
2
3
4
5
6
7
[root@localhost datas]$ touch sort.sh
[root@localhost datas]$ vim sort.sh
bb:40:5.4
bd:20:4.2
xz:50:2.3
cls:10:3.5
ss:30:1.6
  1. 按照“:”分割后的第三列倒序排序。
1
2
3
4
5
6
[root@localhost datas]$ sort -t : -nrk 3  sort.sh
bb:40:5.4
bd:20:4.2
cls:10:3.5
xz:50:2.3
ss:30:1.6

第 11 章 企业真实面试题

11.1 京东

问题 1:使用 Linux 命令查询 file1 中空行所在的行号

答案:

1
2
[root@localhost datas]$ awk '/^$/{print NR}' sed.txt
5

问题 2:有文件 chengji.txt 内容如下:

1
2
3
张三 40
李四 50
王五 60

使用 Linux 命令计算第二列的和并输出

1
2
[root@localhost datas]$ cat chengji.txt | awk -F " " '{sum+=$2} END{print sum}'
150

11.2 搜狐&和讯网

问题 1:Shell 脚本里如何检查一个文件是否存在?如果不存在该如何处理?

1
2
3
4
5
6
7
#!/bin/bash

if [ -f file.txt ]; then
echo "文件存在!"
else
echo "文件不存在!"
fi

11.3 新浪

问题 1:用 shell 写一个脚本,对文本中无序的一列数字排序

1
2
3
4
5
6
7
8
9
10
11
[root@localhost ~]# cat test.txt
9
8
7
6
5
4
3
2
10
1
1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost ~]# sort -n test.txt|awk '{a+=$0;print $0}END{print "SUM="a}'
1
2
3
4
5
6
7
8
9
10
SUM=55

11.4 金和网络

问题 1:请用 shell 脚本写出查找当前文件夹(/home)下所有的文本文件内容中包含有字符”shen”的文件名称

1
2
3
[root@localhost datas]$ grep -r "shen" /home | cut -d ":" -f 1
/home/root/datas/sed.txt
/home/root/datas/cut.txt




本文参考列表