什么是 shell
shell 就是命令解释器,用于解释用户对操作系统的操作,比如当我们在终端上执行 ls ,然后回车,这个时候会由 shell 来解释这个命令,并且执行解释后的命令,进而对操作系统进行操作。
在 Centos 操作系统中支持多种 shell,我们可以通过下面的命令来查看一个操作系统支持的 shell :
powershell
cat /etc/shells
但是 Centos 7 默认的 shell 是 bash,也是在 Centos 系统中常用的 shell。
当我们在终端上执行命令,这个命令就是通过 bash shell 来解释执行的,实际上,当我们打开一个终端实际上也就是打开一个 bash 进程,可以通过 ps 来验证,如下:
powershell
ps -f
## 输出如下:
UID PID PPID C STIME TTY TIME CMD
root 2531 2527 0 14:58 pts/1 00:00:00 -bash
root 2566 2531 0 15:14 pts/1 00:00:00 ps -f
以上 PID 为 2531 就是进程 bash 的 pid。所以我们在这个终端上执行的任何命令都会被 bash shell 进行解释执行。
什么是 shell 编程
在 Linux 操作系统中,有一个非常重要的原则,那就是:一个命令只做一件事情。比如:
powershell
## 进入到指定的目录
cd /var
## 查看当前目录下的所有文件
ls
## 查看当前目录所在的文件路径
pwd
## 我们还可以再统计出当前目录的大小
du -sh
以上说明了一个命令只做一件事情
那么,现在假设我们需要经常依次执行上面的命令,我们如果每次都去输入上面的 4 个命令的话,那就显的太麻烦了,那么我们可以按照如下的 2 步来解决这个问题:
- 将上面的 4 个命令写到一行中:
powershell
cd /var; ls; pwd; du -sh
- 将上面的一行命令保存在一个文件中,一般放 linux 命令的文件的后缀使用
.sh
,执行:
powershell
vi 1.sh
## 将下面的内容保存到 1.sh 文件名中
cd /var; ls; pwd; du -sh
这样的话,如果你想重复执行上面的 4 个命令的时候,你只需要执行 1.sh
文件就可以了,你也可以将这个文件给别人执行。
现在要执行上面的文件,我们需要先给脚本赋予执行权限,因为默认的时候文件是没有执行权限的,如下:
powershell
chmod u+x 1.sh
我们可以使用 bash
命令来执行上面的 1.sh
文件,如下:
powershell
bash 1.sh
那么执行上面的命令的结果和上面执行 4 条命令的结果是一样的。
那么上面的 1.sh
就是一个 shell 脚本,编写 shell 脚本其实就是 shell 编程。
在上面的 1.sh
中可以将分号去掉,然后一个命令一行,如下:
powershell
cd /var
ls
pwd
du -sh
这样效果是一样的。
总结:
- shell 就是命令解释器,对用户输入的命令解释并执行
- shell 脚本就是用户为了完成某一件事,而将多个命令放在一个文件中,然后直接执行脚本文件就可以达到目的。
shell 的执行方式
执行一个 shell 脚本的方式有 4 种:
bash script.sh
./script.sh
source script.sh
. script.sh
我们先写一个名为 2.sh
的脚本来对比上面执行脚本的方式的异同点,脚本内容如下:
powershell
cd /tmp
pwd
bash 命令执行脚本
当我们使用 bash 来执行 2.sh
:
powershell
bash 2.sh
输出如下:
可以看出:
- bash 命令可以执行没有执行权限的脚本
- 当 bash 执行脚本后,当前的目录还是在
/root
主目录下,并没有切换到/tmp
目录中。 - 这个是因为,当执行 bash 命令的时候,会启动一个新的进程来执行脚本中的命令,所以脚本执行的结果对当前进程是没有影响的
我们在终端上执行 bash
命令,然后使用 ps -f
查看也能验证这点:
以上的进程是执行 bash 命令后出现的进程。而且还能看出这个 bash 进程的父亲进程的 ID 是第一个 bash 进程的 PID。
./ 执行脚本
当我们使用 ./
来执行 2.sh
:
powershell
./2.sh
结果输出:
powershell
-bash: ./2.sh: Permission denied
发现权限不够,使用这种方式来执行脚本的时候,要求这个脚本要有可执行权限,如下更改脚本的权限:
powershell
chmod u+x 2.sh
再次执行脚本,输出如下:
这个输出和使用 bash 命令执行脚本的输出是一样的:
- 同样会创建一个新的进程来执行脚本中的内容
- 而且使用这种方式来执行的话,脚本要求有可执行权限
source 和 . 来执行脚本
如下使用 source
来执行脚本:
powershell
source 2.sh
输出如下:
如下使用 .
来执行脚本:
powershell
. 2.sh
输出如下:
可以看出,以上两种方式执行结果是一样的,而且对当前终端是有影响的,两种执行结果都进入了 /tmp
目录,这个可以说明:
- 使用
source
和.
执行脚本的时候不会启动先的进程,只是在当前的 bash 进程中执行脚本内容 - 所以这两种执行脚本的方式在执行脚本的时候会影响当前终端。
变量
变量的定义
在 shell 编程中,有很多时候我们想将数据先临时保存起来,供后续使用,我们可以先将数据保存到某个变量中,如下是将字符串 jeffy 赋值给变量 name :
powershell
name=jeffy
注意:等号两边不能有空格
以上就是定义了一个名字为 name 的变量,我们可以通过如下的方式来访问这个变量:
powershell
## 在控制台中输出变量 name 的值
echo ${name}
## 以上访问 name 变量也可以简写为:
echo $name
在定义变量的时候,变量名的定义需要遵循下面的规则:
- 由字母、数字、下划线组成
- 不以数字开头
- 变量名一般要求有一定的意义
比如下面的变量名是不推荐使用的:
powershell
## 存在非法组成字符
*name=twq
## 以数字开头了
12name=twq
## a 这个变量名没啥意义
a=twq
现在如果我们想将 10+20 的结果赋值给一个名为 result 的变量,如下:
powershell
result=10+20
echo $result
## 以上输出为
10+20
上面的程序并没有达到我们的预期,我们可以在定义变量之前加上一个关键字 let
,如下:
powershell
let result=10+20
echo $result
## 以上输出为
30
在有些场景下,变量值中可能会出现空格等其他的特殊字符,我们可以通过双引号或者单引号来解决特殊字符的问题,如下:
powershell
## 定义一个名为 content 的变量,将 tom's cat 赋值给 content 变量
## 以下定义是错误的,因为变量值中有空格
content=tom's cat
## 我们在变量值中加上双引号,如下
content="tom's cat"
echo $content
## 如果变量值中有一个双引号的话,则需要使用单引号,比如
content='tom"s cat'
echo $content
变量和命令
除了数据,我们也可以将一个命令赋值给一个变量,如下:
powershell
## 将 ls 命令赋值给变量 l
l=ls
## 这样使用:
$l
以上的做法意义不大,在实际生产上,我们一般不会将一个命令赋值给一个变量
为了提高性能,我们一般会将一个命令执行的结果赋值给一个变量,如下:
powershell
## 将 ls -l /etc 执行的结果赋值给变量 lsetc :
lsetc=$(ls -l /etc)
## 以后想使用 ls -l /etc 的结果的话,我们只需要访问 lsetc 这个变量就可以了
echo $lsetc
## 我们也可以使用 `` 来代替上面的 $()
lsetc=`ls -l /etc`
echo lsetc
变量的拼接
比如,我们先定义一个名为 tmp
的临时变量,我们将字符串 test
赋值给 tmp
:
powershell
tmp=test
如果我们想将 tmp
中的变量值和字符串 twq
进行拼接:
powershell
echo $tmptwq
## 输出为空
使用上面的方式是不能进行变量拼接的,以上 tmptwq 被看成了一个变量,然而这个变量并没有赋值,所以输出为空,我们可以通过如下的方式进行变量的拼接:
powershell
echo ${tmp}twq
## 输出为:
testtwq
## 我们也可以将拼接之后的数据再次赋值给变量 tmp :
tmp=${tmp}twq
echo $tmp
实际上我们可以在使用变量的时候,在变量后面加上一个非数字、非字母、非下划线的字符就可以实现变量的拼接,如下:
powershell
echo $tmp:jeffy
## 输出:
testtwq:jeffy
我们再定义一个变量:
powershell
age=20
然后将 tmp
和 age
变量使用 :
拼接起来,如下:
powershell
echo $tmp:$age
## 输出为
testtwq:20
变量的范围
我们现在先定义一个名为 demo_var
的变量,这个变量的值是 hello shell
,如下:
powershell
demo_var="hello shell"
## 在当前的 bash 进程中是可以访问的,如下:
echo $demo_var
## 如果我们再打开一个 bash 进程
bash
## 然后就访问不了了变量了,如下输出为空:
echo $demo_var
## 我们再在 bash 子进程中定义一个变量,如下:
demo_var="hello subshell"
## 然后退出当前的子 bash 进程
exit
## 在父进程中也访问不了子进程中定义的变量
echo $demo_var
## 以上输出为:
hello shell
## 如果我们重新打开一个终端,那也是访问不了上面的进程中的变量
从上面的演示,可以得出结论:
- 变量默认的访问范围是当前的 bash 进程
那么我们如果在某个脚本中能不能访问上面的变量呢?我们先创建一个名为 3.sh
的脚本,内容就是访问并打印变量 demo_var
的值,如下:
powershell
vi 3.sh
echo $demo_var
## 保存以上的 2.sh 的脚本文件
## 给 2.sh 脚本赋予执行权限
chmod u+x 2.sh
然后我们分别使用 bash 2.sh
、./2.sh
、source 2.sh
以及 . 2.sh
四种方式来执行上面的脚本,发现:
- 使用
bash 2.sh
、./2.sh
这两种方式访问不到变量的值,这个是因为这两种方式都会启动一个子 bash 进程来执行脚本 - 而使用
source 2.sh
、. 2.sh
这两种方式是可以访问到变量的值,这个是因为这两种方式都是在当前的 bash 进程中执行脚本的
那么我们怎么样使得一个变量可以在子 bash 进程中访问呢?我们可以使用 export
关键词将变量导出,然后其他的进程就可以访问这个变量了,如下:
powershell
## 在定义 demo_var 的 bash 进程中 export 这个变量:
export demo_var
## 然后使用四种方式执行 2.sh 脚本时都是可以访问变量的
## 也就是说变量通过 export 导出后,就可以在子进程中进行访问了。
## 如果你想取消导出的变量,可以使用关键字 unset 来实现,如下
unset demo_var
环境变量及其配置文件
环境变量
所谓的环境变量就是每一个 shell 打开都可以获得到的变量,只要一打开一个终端,就可以访问环境变量。
我们可以通过下面的命令获取到当前用户下当前默认的所有的环境变量:
powershell
env
输出为:
powershell
XDG_SESSION_ID=8
HOSTNAME=master
SELINUX_ROLE_REQUESTED=
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=192.168.126.1 59094 22
SELINUX_USE_CURRENT_RANGE=
SSH_TTY=/dev/pts/0
USER=root
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
PWD=/root
LANG=en_US.UTF-8
SELINUX_LEVEL_REQUESTED=
HISTCONTROL=ignoredups
SHLVL=1
HOME=/root
LOGNAME=root
SSH_CONNECTION=192.168.126.1 59094 192.168.126.133 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
XDG_RUNTIME_DIR=/run/user/0
_=/usr/bin/env
上面显示的都是 root
用户下的环境变量,变量名都是使用大写来表示的。在以上的环境变量中有几个需要我们关心的环境变量:
- USER
powershell
## 输出当前用户
echo $USER
- UID
powershell
## 输出当前用户 id
echo $UID
- PATH
powershell
## 输出当前用户下的命令搜索路径 PATH 的变量值
echo $PATH
输出为:
powershell
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
以上路径称为命令搜索路径,当我们执行一个命令,比如 ls、cd
等,系统会从上面的路径下分别搜索这个命令对应的脚本文件:
- 先搜索
/usr/local/sbin
- 再搜索
/usr/local/bin
- 再搜索
/usr/sbin
- 再搜索
/usr/bin
- 最后搜索
/root/bin
如果第一次搜索到了就执行那个目录下的脚本命令,比如当执行 ls
命令的时候,系统会依次看
/usr/local/sbin
下面是否有 ls 脚本文件/usr/local/bin
下面是否有 ls 脚本文件/usr/sbin
下面是否有 ls 脚本文件/usr/bin
下面是否有 ls 脚本文件/root/bin
下面是否有 ls 脚本文件
发现 ls
在 /usr/bin
下面,所以最终执行的就是 /usr/bin/ls
脚本文件:
- 实际上执行
ls
和/usr/bin/ls
的效果是一样的 - 就是因为
/usr/bin
在PATH
变量中,所以执行ls
命令的时候可以省去完整的路径, - 而且你可以在任何的位置上都可以执行
ls
命令
比如,我们在 /tmp
目录下创建一个名为 4.sh
的脚本,内容如下:
powershell
echo "hello bash"
du -sh
然后赋予 4.sh
这个文件的执行权限:
powershell
chmod u+x /tmp/4.sh
我们在 /root
目录下可以直接执行 ./4.sh
,可以执行成功,但是如果我们直接使用 4.sh
来运行的话,是执行不成功的,这个是因为在命令搜索路径中搜索不到 /tmp
目录下的 4.sh
,如果我们想直接 4.sh
执行成功的话,需要将 4.sh
所在的目录拼接到环境变量 PATH
中,如下:
powershell
PATH=$PATH:/tmp
echo $PATH
输出为:
powershell
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/tmp
现在,就可以在任意的目录中都可以直接执行 4.sh
了。
以上更改之后的 PATH 对子进程是有效的,但是对于新打开的终端是无效的。
bash shell 的格式
在我们执行 bash 命令的时候,实际上也需要从命令搜索路径中搜索这个 bash 命令所在的路径,然后执行这个命令,我们可以通过 which
命令来查看 bash 命令是哪一个路径下的:
powershell
which bash
## 输出如下:
/usr/bin/bash
说明 bash 命令默认使用的是 /usr/bin/bash
那么如果使用 bash 命令执行脚本的时候,默认就是使用 /usr/bin/bash
这个命令来执行的,那么当我们使用其他方式来执行脚本的时候,到底是使用什么命令来执行脚本的呢?
当然,默认的话还是使用 /usr/bin/bash
来执行的,但是如果你想指定使用 /bin/bash
来执行脚本的话,可以在脚本的前面加上一个声明:
powershell
#!/bin/bash
加了上面的声明后
- 如果使用 bash 命令来执行的话,那么上面的声明就成了注释,使用的还是系统默认的 bash
- 如果使用其他方式来执行脚本的话,那么上面的声明就是告诉使用
/bin/bash
来执行这个脚本中的内容
环境变量配置文件
在 Centos 7 中和环境变量相关的配置文件有如下几个:
/etc/profile
~/.bash_profile
~/.bashrc
/etc/bashrc
一般的话我们的环境变量都保存在以上的 4 个配置文件中,那么保存在 /etc 下面的配置文件中的环境变量是所有的用户都可以访问到的,而保存在用户主目录下配置文件中的环境变量只有当前的用户可以访问的
那么对于一些通用的环境变量的话,一般保存在 /etc 文件目录下的配置文件,而对于某个用户特用的则保存在这个用户下的家目录下的配置文件中。
除了以上的区别之外,我们还可以看出以上 4 个文件主要分为两类,一类是 profile
,一类是 bashrc
。那么这个 profile
主要是用于 login shell
的,而 bashrc
主要是用于 nologin shell
的,接下来我们分别在以上 4 个配置文件中使用 echo
打印输出一句话,然后看看 4 个配置文件的执行顺序。
在文件 /etc/profile
中加入:
powershell
echo "/etc/profile starting"
在文件 /etc/bashrc
中加入:
powershell
echo "/etc/bashrc starting"
在 root
家目录下 ~/.bashrc
下加入:
powershell
echo "~/.bashrc starting"
在 root
家目录下 ~/.bash_profile
中加入:
powershell
echo "~/.bash_profile starting"
在 user1
家目录下 ~/.bashrc
下加入:
powershell
echo "user1 ~/.bashrc starting"
在 user1
家目录下 ~/.bash_profile
中加入:
powershell
echo "user1 ~/.bash_profile starting"
现在我们做以下的实验:
- 重启客户端,重新连接终端,发现执行顺序是:
powershell
/etc/profile starting
~/.bash_profile starting
~/.bashrc starting
/etc/bashrc starting
su - user1
powershell
/etc/profile starting
user1 ~/.bash_profile starting
user1 ~/.bashrc starting
/etc/bashrc starting
su - root
powershell
/etc/profile starting
~/.bash_profile starting
~/.bashrc starting
/etc/bashrc starting
su user1
powershell
user1 ~/.bashrc starting
/etc/bashrc starting
su root
powershell
~/.bashrc starting
/etc/bashrc starting
接下来对上面进行总结:
login shell
的执行顺序:/etc/profile -> ~/.bash_profile -> ~/.bashrc -> /etc/bashrc
nologin shell
的执行顺序:~/.bashrc -> /etc/bashrc
现在为了使得在任何的终端上都可以执行 4.sh
,我们重新打开一个终端,然后将环境变量写入到配置文件 ~/.bash_profile
中,如下:
powershell
PATH=$PATH:$HOME/bin:/tmp
修改配置后,还不能直接执行 4.sh
,因为更改后的配置在当前的 bash 进程不生效,如果我们想使得配置在当前的 bash 进程中生效的话,可以执行命令:
powershell
source .bash_profile
这样就可以在 root
的任意目录下执行 4.sh
了,如果你想在其他的用户的任意目录中执行 4.sh
的话,可以在 /etc/profile
的配置中增加配置:
powershell
PATH=$PATH:/tmp
预定义变量和位置变量
预定义变量
常见的预定义变量包括:$?
、$$
、$0
$?
表示判断上一条执行的命令是否正常执行,如果$?
返回的值是0
的话,则表示上一条命令是正常执行成功,否则表示上一条命令执行失败
powershell
ifconfig
echo $?
## 返回 0
## 查询一个不存在的网卡
ifconfig em
echo $?
## 返回 1
$$
表示返回当前进程的PID
powershell
echo $$
$0
表示返回当前进程的名称
powershell
echo $0
创建一个 5.sh
的脚本,脚本内容如下:
powershell
#!/bin/bash
# 显示 PID 和 PName
echo $$
echo $0
给脚本赋值权限:
powershell
chmod u+x 5.sh
## 分别使用两种方式来执行脚本
## 1. 使用 bash
bash 5.sh
## 2. 使用 . 来执行
. 5.sh
## 第二种方式输出的进程号和进程名是当前的 bash 进程的
位置变量
位置变量用于给脚本传递参数的变量。我们可以给一个脚本传递任意多的参数:
$1
表示第一个参数$2
表示第二个参数$3
表示第三个参数- ...
${10}
表示第十个参数${11}
表示第十一个参数- ...
我们现在写一个脚本 6.sh
,如下:
powershell
#!/bin/bash
# Program:
# Program shows the script name, parameters...
first=${1}
second=${2}
echo "The script name is ==> ${0}"
echo "The 1st parameter ==> $first"
echo "The 2nd parameter ==> $second"
然后给 5.sh
执行权限: chmod u+x 6.sh
使用下面的命令执行上面的脚本:
powershell
bash 6.sh -a -l
输出如下:
powershell
The script name is ==> 6.sh
The 1st parameter ==> -a
The 2nd parameter ==> -l
如果我们我们只传递了一个参数:
powershell
bash 6.sh -a
输出为:
powershell
The script name is ==> 6.sh
The 1st parameter ==> -a
The 2nd parameter ==>
可以发现第二个参数的值为空了,如果没有传递参数的话,我们可以使用默认值来代替,脚本内容修改后如下:
powershell
#!/bin/bash
# Program:
# Program shows the script name, parameters...
first=${1}
second=${2-_}
echo "The script name is ==> ${0}"
echo "The 1st parameter ==> $first"
echo "The 2nd parameter ==> $second"
再此执行脚本:
powershell
bash 6.sh -a
输出为:
powershell
The script name is ==> 6.sh
The 1st parameter ==> -a
The 2nd parameter ==> _
传递 2 个参数来执行脚本:
powershell
bash 6.sh -a -l
输出如下:
powershell
The script name is ==> 6.sh
The 1st parameter ==> -a
The 2nd parameter ==> -l
在给脚本传递参数的时候,还有两个预定义变量:
$#
表示参数的个数$@
表示拿到所有的参数
修改 6.sh
脚本如下:
powershell
#!/bin/bash
# Program:
# Program shows the script name, parameters...
# History:
# 2018/03/20 twq
first=${1}
second=${2-_}
echo "The script name is ==> ${0}"
echo "Total parameter number is ==> $#"
echo "Your whole parameter is ==> '$@'"
echo "The 1st parameter ==> $first"
echo "The 2nd parameter ==> $second"
传递 2 个参数来执行脚本:
powershell
bash 6.sh -a -l
输出如下:
powershell
The script name is ==> 6.sh
Total parameter number is ==> 2
Your whole parameter is ==> '-a -l'
The 1st parameter ==> -a
The 2nd parameter ==> -l
数组
变量可以存储任意的字符串数据,如果需要存储任意多个具有相同意思的字符串数据的话,我们可以使用数组来存储。
比如,我们定义一个数组用于存储 3 个 ip
:
powershell
ips=(10.0.0.1 10.0.0.2 10.0.0.3)
如果我们想访问数组中所有的元素的话,可以:
powershell
echo ${ips[@]}
如果想显示数组的个数的话,可以:
powershell
echo ${#ips[@]}
如果想访问指定下标的元素的话可以:
powershell
## 访问第一个元素的值
echo ${ips[0]}
## 访问第二个元素的值
echo ${ips[1]}
数字运算
在 Linux 脚本中支持:
- 赋值运算符
- 算术运算符
赋值运算符很简单,就是使用 =
来进行赋值操作。
算术运算符支持 + - * / %
这 5 种运算符,我们接下来详细讲解下在 Linux 中怎么来完成算术运算。
如果我们想在 shell 脚本中完成将 4 + 5 的结果赋值给一个名为 result 的变量,我们该怎么做呢?
powershell
## 直接这样做是不行的
result=4+5
echo result
## 需要使用 expr 这个命令来实现运算
## 这里需要注意的是 4 + 5 之间必须要有空格
expr 4 + 5
## 将 expr 命令计算出来的结果赋值给 result 的变量
result=`expr 4 + 5`
echo $result
expr
命令不支持小数的运算
除了使用 expr
命令,还可以使用双圆括号来进行运算:
powershell
(( result=4+5 ))
echo $result
(( result++ ))
echo $result
test 比较
exit
exit
命令用于退出脚本的,如果是正常退出的话,exit
会返回 0
,如果是异常退出的话,则 exit
会返回非 0
现在我们编写一个 7.sh
的脚本,脚本的内容如下:
powershell
#!/bin/bash
pwd
exit
执行上面的脚本,发现加不加 exit
都是一样的,我们执行 :
powershell
## 查看脚本返回的值为 0
echo $?
但是当脚本发生错误的时候,比如执行一个不存在的命令:
powershell
#!/bin/bash
ppwd
exit
这个时候执行脚本报错了:
powershell
## 查看脚本返回的值为 127
echo $?
我们在执行 exit
命令的时候,也可以指定脚本退出返回的值,如下:
powershell
#!/bin/bash
pwd
exit 127
这个时候不管脚本有没有报错,都返回 127
:
powershell
## 查看脚本返回的值为 127
echo $?
test 命令
我们使用 man
来获取 test
命令的帮助文档:
powershell
man test
发现,我们使用 test
主要可以做如下的事情:
- 测试两个字符串是否相等
- 测试两个整数之间的大小关系
- 测试一个文件是否存在
测试两个字符串是否相等
powershell
[root@master ~]# test "abc" = "abc"
[root@master ~]# echo $?
0
[root@master ~]# test "abc" = "abcd"
[root@master ~]# echo $?
1
[root@master ~]# test "abc" != "abcd"
[root@master ~]# echo $?
0
注意:0
表示 true
,非 0
表示 false
以上的写法我们还可以这样写:
powershell
[root@master ~]# [ "abc" = "abc" ]
[root@master ~]# echo $?
0
[root@master ~]# [ "abc" = "abcd" ]
[root@master ~]# echo $?
1
[root@master ~]# [ "abc" != "abcd" ]
[root@master ~]# echo $?
0
整数大小判断
在判断两个整数的大小的时候,我们使用:
- -eq 表示判断是否等于 (equal)
- -ge 表示判断是否大于等于 (great than or equal)
- -gt 表示判断是否大于 (great than)
- -le 表示判断是否小于等于 (less than or equal)
- -lt 表示判断是否小于 (less than)
- -ne 表示判断是否不等于 (not equal)
powershell
[root@master ~]# test 5 -gt 4
[root@master ~]# echo $?
0
[root@master ~]# test 5 -eq 4
[root@master ~]# echo $?
1
[root@master ~]# test 5 -lt 4
[root@master ~]# echo $?
1
以上的判断还可以这样写:
powershell
[root@master ~]# [ 5 -gt 4 ]
[root@master ~]# echo $?
0
[root@master ~]# [ 5 -eq 4 ]
[root@master ~]# echo $?
1
[root@master ~]# [ 5 -lt 4 ]
[root@master ~]# echo $?
1
如果你想用 >、< 、=
等这种比较符号的话,需要使用双中括号来实现:
powershell
[root@master ~]# [[ 5 > 4 ]]
[root@master ~]# echo $?
0
[root@master ~]# [[ 5 = 4 ]]
[root@master ~]# echo $?
1
[root@master ~]# [[ 5 < 4 ]]
[root@master ~]# echo $?
1
测试一个文件是否存在
- 测试一个文件是否存在
powershell
[root@master ~]# test -e /etc/passwd
[root@master ~]# echo $?
0
[root@master ~]# test -e /etc/passwd2
[root@master ~]# echo $?
1
也可以写成:
powershell
[root@master ~]# [ -e /etc/passwd ]
[root@master ~]# echo $?
0
[root@master ~]# [ -e /etc/passwd2 ]
[root@master ~]# echo $?
1
- 测试一个文件是否存在,并且这个文件是目录
powershell
[root@master ~]# test -d /etc
[root@master ~]# echo $?
0
[root@master ~]# test -d /etc/passwd
[root@master ~]# echo $?
1
也可以写成:
powershell
[root@master ~]# [ -d /etc ]
[root@master ~]# echo $?
0
[root@master ~]# [ -d /etc/passwd ]
[root@master ~]# echo $?
1
- 测试一个文件是否存在,并且这个文件是普通文件
powershell
[root@master ~]# test -f /etc/passwd
[root@master ~]# echo $?
0
[root@master ~]# test -f /etc
[root@master ~]# echo $?
1
也可以写成:
powershell
[root@master ~]# [ -f /etc/passwd ]
[root@master ~]# echo $?
0
[root@master ~]# [ -f /etc ]
[root@master ~]# echo $?
1
多个测试进行逻辑与和逻辑或
powershell
[root@master ~]# [[ -f /etc && 5 -gt 4 ]]
[root@master ~]# echo $?
1
[root@master ~]# [[ -f /etc || 5 -gt 4 ]]
[root@master ~]# echo $?
0
也可以写成:
powershell
[root@master ~]# [ -f /etc ] && [ 5 -gt 4 ]
[root@master ~]# echo $?
1
[root@master ~]# [ -f /etc ] || [ 5 -gt 4 ]
[root@master ~]# echo $?
0
条件语句 if case while for
if...then
powershell
if [ 条件判断式 ]; then
当条件判断式返回 0 的时候,可以进行的命令工作内容
fi
写一个脚本来判断当前的用户是否是 root 用户,脚本名为 8.sh
,内容如下:
powershell
#!/bin/bash
if [ $USER = 'root' ]; then
echo "the current user is root"
fi
if...then...else
powershell
if [ 条件判断式 ]; then
当条件判断式返回 0 的时候,可以进行的命令工作内容
else
当条件判断式返回非 0 的时候,可以进行的命令工作内容
fi
修改脚本 8.sh
,内容如下:
powershell
#!/bin/bash
if [ $USER = 'root' ]; then
echo "the current user is root"
else
echo "the current user is not root"
fi
if...then...elif...
powershell
if [ 条件判断式一 ]; then
当条件判断式一返回 0 的时候,可以进行的命令工作内容
elif [ 条件判断式二 ]; then
当条件判断式二返回 0 的时候,可以进行的命令工作内容
else
当所有条件判断式都返回非 0 的时候,可以进行的命令工作内容
fi
新增 9.sh
脚本,脚本内容如下:
powershell
#!/bin/bash
read -p "Please input (Y/N):" yn
if [[ "$yn" = "Y" || "$yn" = "y" ]]; then
echo "Ok, continue"
elif [[ "$yn" = "N" || "$yn" = "n" ]]; then
echo "Oh, interrupt!"
else
echo "not support!!"
fi
编写一个名为 10.sh
的 shell 脚本,shell 脚本中的内容设计为:
- 判断传递给脚本的第一个参数的值,如果这个值等于 hello 的话,就显示 "Hello, how are you ?"
- 如果没有任何参数的话,就提示用户必须要使用的参数
- 如果传递的参数不是 hello ,就提醒用户仅能使用 hello 作为参数
powershell
#!/bin/bash
if [ "$1" = "hello" ]; then
echo "Hello, how are you ?"
elif [ "$1" = "" ]; then
echo "You must input parameters, for exapme > {$0 someword}"
else
echo "The only parameter is 'hello', for example > {$0 hello}"
fi
case...esac
case...esac 用来进行选择执行,语法如下:
powershell
case $变量名称 in
"第一个值")
变量内容等于第一个值的时候执行的程序段
;;
"第二个值")
变量内容等于第二个值的时候执行的程序段
;;
*)
不等于第一个值且不等于第二个值的其他程序执行内容
exit 1
;;
esac
我们可以将上一节课的 10.sh
脚本使用 case
来实现,新建一个名为 11.sh
的脚本,内容如下:
powershell
#!/bin/bash
case $1 in
"hello")
echo "Hello, how are you ?"
;;
"")
echo "You must input parameters, for exapme > {$0 someword}"
;;
*)
echo "The only parameter is 'hello', for example > {$0 hello}"
;;
esac
我们在前面管理系统服务的时候,一般我们都使用 service start|stop|restart
等来对服务来管理,我们现在也可以自己来写一个 service.sh
脚本,来默认对服务的启停等管理,脚本内容如下:
powershell
#!/bin/bash
case $1 in
"start")
echo "service start"
;;
"stop")
echo "service stop"
;;
"restart")
echo "service restart"
;;
"status")
echo "service status"
;;
*)
echo "Usage : {$0 start|stop|restart|status}"
;;
esac
while
while
的语法是:
powershell
while [ condition ]
do
程序段落
done
以上的说法是:当 condition
成立时,就进行循环,直到 condition
的条件不成立才停止。
我们现在来写一个名为 12.sh
的脚本,让用户输入 yes
或者 YES
的时候才结束程序,否则就一直进行告知用户输入字符串:
powershell
#!/bin/bash
while [[ "$yn" != "yes" && "$yn" != "YES" ]]
do
read -p "Please input yes/YES to stop this program:" yn
done
echo "Ok! program stopping"
还有一个循环和 while
刚好是相反的,那就是 until
:
powershell
until [ condition ]
do
程序段落
done
以上的说法是:当 condition
不成立时,就进行循环,直到 condition
的条件成立才停止。
我们现在来写一个名为 13.sh
的脚本,让用户输入 yes
或者 YES
的时候才结束程序,否则就一直进行告知用户输入字符串:
powershell
#!/bin/bash
until [[ "$yn" == "yes" || "$yn" == "YES" ]]
do
read -p "Please input yes/YES to stop this program:" yn
done
echo "Ok! program stopping"
练习: 使用 while
来计算 1
到 100
之和
powershell
#!/bin/bash
s=0
i=0
while [ "$i" != "100" ]
do
((i++))
((s=$s+$i))
done
echo "the result is : $s"
练习: 使用 while
来计算 1
到你输入的数字之和,比如:
- 你输入 10 ,就计算 1 到 10 之和
- 你输入 100 ,就计算 1 到 100 之和
- 你输入 1000 ,就计算 1 到 1000 之和
- ...
powershell
#!/bin/bash
read -p "Please input you number :" num
s=0
i=0
while [ "$i" != "$num" ]
do
((i++))
((s=$s+$i))
done
echo "the result is : $s"
for
循环遍历指定的值
powershell
for var in con1 con2 con3 ...
do
程序段
done
比如程序段:
powershell
for i in a b c
do
echo $i
done
循环指定范围
如下代码段:
powershell
for i in {1..9}
do
echo $i
done
还比如:
powershell
for i in $(seq 1 10)
do
echo $i
done
循环目录中的所有文件
powershell
filelist=$ (ls /)
echo $filelist
for filename in $filelist
do
echo ${filename}_file
done
c 语言风格的 for
语法如下:
powershell
for (( 初始值; 限制值; 执行步长 ))
do
程序段
done
使用上面的 for
语句来计算 1
到 100
的累加值,先建名为 14.sh
的脚本,内容为 :
powershell
#!/bin/bash
s=0
for (( i=1; i<=100; i=i+1 ))
do
s=$(($s+$i))
done
echo "the result is : $s"
函数
现在假设有个需求:
- 输入一个目录,然后打印出这个目录下的所有的普通文件
我们创建一个名为 list_file_for_dir.sh
的脚本,内容如下:
powershell
#!/bin/bash
function is_file() {
test -f $1
}
function list_file() {
file_list=`ls $1`
for filename in $file_list
do
if is_file $1/$filename; then
echo $1/$filename
fi
done
}
这个时候我们可以写一个名为 15.sh
的脚本然后调用上面的函数:
powershell
#!/bin/bash
## 导入需要调用的函数所在的脚本
source ./list_file_for_dir.sh
dir=$1
list_file $dir
重定向
当我们在 Centos 7 系统上启动一个进程的话,默认的话会打开标准输入、标准输出、错误输出三个文件描述符。
比如当我们打开一个终端的时候,会启动一个 bash 进程,我们可以通过 ps
来查看这个进程的 PID
:
powershell
[root@master ~]# ps
PID TTY TIME CMD
2063 pts/1 00:00:00 bash
2080 pts/1 00:00:00 ps
然后我们可以查看目录 /proc/2033/fd
下的文件:
powershell
[root@master fd]# ls -l /proc/2033/fd
total 0
lrwx------. 1 root root 64 Oct 6 15:19 0 -> /dev/pts/0
lrwx------. 1 root root 64 Oct 6 15:19 1 -> /dev/pts/0
lrwx------. 1 root root 64 Oct 6 15:19 2 -> /dev/pts/0
lrwx------. 1 root root 64 Oct 6 15:20 255 -> /dev/pts/0
/proc
目录下就是存放了每个进程的状态信息
从上可以看出有 4 个链接文件,其中:
- 0 表示标准输入
- 1 表示标准输出
- 2 表示错误输出
从上还可以看出,不管是输入还是输出,默认都是终端。
不管是输入还是输出,我们都可以进行重定向。
输入重定向
powershell
## 使用 wc -l 统计从终端输入的数据的行数
[root@master ~]# wc -l
123
2342
2
[root@master ~]#
ctrl + d 退出 wc -l 界面
我们可以使用输入重定向符号 <
来对输入进行重定向,比如我们使用 wc -l
统计一个文件中的数据的行数,如下:
powershell
## 将输入重定向为一个文件
[root@master ~]# wc -l < /etc/passwd
22
还比如,我们可以读取终端的数据,饭后赋值给一个变量:
powershell
[root@master ~]# read var
123
[root@master ~]# echo $var
123
我们也可以将一个文件作为输入,然后将文件中第一行内容赋值给一个变量:
powershell
[root@master ~]# read var2 < /etc/passwd
[root@master ~]# echo $var2
root:x:0:0:root:/root:/bin/bash
输出重定向
默认情况下输出是终端,比如:
powershell
## 将数据字符串 123 输出到终端
[root@master ~]# echo 123
123
如果我们想将输出重定向到一个文件中,我们可以使用 >
或者 >>
符号,如下:
powershell
[root@master ~]# echo 123 > a.txt
[root@master ~]# cat a.txt
123
[root@master ~]# echo 456 > a.txt
[root@master ~]# cat a.txt
456
[root@master ~]# echo 456 >> a.txt
[root@master ~]# cat a.txt
456
456
[root@master ~]# echo 456 >> a.txt
[root@master ~]# cat a.txt
456
456
456
>
会清除文件中之前的内容>>
不会清除文件,会将内容追加到文件的最后
以上是将正确的数据输入到指定的文件,我们也可以将一个执行脚本或者命令时候的报错信息输出到指定的文件中,比如:
powershell
[root@master ~]# nocmd
-bash: nocmd: command not found
[root@master ~]# nocmd 2> error.tx
[root@master ~]# cat error.tx
-bash: nocmd: command not found
可以看出我们使用 2>
来重定向错误输出。
如果不管是正确结果还是错误结果,我们都想重定向到一个指定文件中,我们可以使用 &>
符号:
powershell
[root@master ~]# nocmd &> d.txt
[root@master ~]# cat d.txt
-bash: nocmd: command not found
[root@master ~]# ls &> d.txt
[root@master ~]# cat d.txt
10.sh
11.sh
12.sh
13.sh
14.sh
15.sh
2.sh
5.sh
nohup 和 输出重定向
我们前面讲过使用 nohup
的方式来启动进程,使得进程在后台运行,并且将进程的输出会输出到当前目录下 nohup.out
的文件中
我们可以使用 nohup
结合输出重定向 &>
来自定义进程的输出文件,如下:
powershell
## 后台运行 15.sh 脚本,并且将结果都输出到 test.out 文件中
nohup /root/15.sh /etc &> test.out &
## 你也可能会遇到这样的写法,效果和上面的是一样
## 先将脚本标准输出输出到文件 test.out 中
## 然后 2>&1 表示将错误输出输出到标准输出中
nohup /root/15.sh > test.out 2>&1 &
管道
执行一个命令或者一个脚本都会有一个标准输入和一个标准输出,有很多场景下我们想将一个命令的标准输出作为另一个命令的标准输入,这个就是我们这篇文章讲到的管道技术。
比如我们执行 ls /etc
的时候发现输出结果太多了,我们想分页看这个命令输出的结果,那么我们就可以将 ls /etc
这个命令的输出作为命令 less
的输入,如下:
powershell
ls /etc | less
上面的 |
就是管道符。它的作用就是将 ls /etc
的输出作为 less
命令的输入。
再比如,我们使用 history
来查看历史命令,但是发现执行的历史命令太多了,我们需要从历史命令中找到包含关键字 alias
的命令,我们可以:
powershell
history | grep alias
也就是将 history
的输出作为 grep alias
的输入,grep alias
就是查找包含 alias
的行。
有的时候我们通过 ps -ef
来查找进程,发现进程数太多了,也可以使用 grep
来过滤出我们自己想要的进程的信息:
powershell
ps -ef | grep wc
date 命令
我们可以使用 date
命令查看系统当前的时间:
powershell
[root@master ~]# date
Sun Oct 6 14:20:26 CST 2019
如果你的时间不对的话,可以通过 date
命令来设置:
powershell
date -s '2019-10-10 08:55:55'
我们也可以通过 date
命令拿到系统当前的年、月、日、时、分、秒:
powershell
[root@master ~]# date +%Y
2019
[root@master ~]# date +%m
10
[root@master ~]# date +%d
06
[root@master ~]# date +%H
14
[root@master ~]# date +%M
21
[root@master ~]# date +%S
16
[root@master ~]# date +%Y%m%d
20191006
有了这些信息后,我们就可以对当前的时间进行格式化,比如我们想使用格式 YYYY-mm-dd HH:MM:SS
来显示当前的时间:
powershell
[root@master ~]# date "+%Y-%m-%d %H:%M:%S"
2019-10-06 14:24:24
[root@master ~]# date +%Y%m%d%H%M%S
20191006142531
我们还可以使用 date
命令获取到前一天的时间:
powershell
[root@master ~]# date --d="1 days ago"
Sat Oct 5 14:28:55 CST 2019
[root@master ~]# date --d="1 days ago" "+%Y-%m-%d %H:%M:%S"
2019-10-05 14:29:08
还可以获取明天或者后天的时间:
powershell
[root@master ~]# date --d="1 days"
Mon Oct 7 14:29:51 CST 2019
[root@master ~]# date --d="1 days" "+%Y-%m-%d %H:%M:%S"
2019-10-07 14:29:58
一次性计划任务
我们前面都是手动的执行脚本,那么在实际的环境中,有可能在半夜、或者指定的时间来运行脚本,这个我们可以通过 at
命令来实现,使用 at
前需要安装 at
:
powershell
yun -y install at
接下来我们使用 at
命令来执行 15.sh
脚本:
powershell
[root@master ~]# chmod u+x 15.sh
[root@master ~]# date
Sun Oct 6 14:37:56 CST 2019
[root@master ~]# at 14:40
at> /root/15.sh /etc > /tmp/test.txt
at> <EOT>
job 4 at Sun Oct 6 14:40:00 2019
注意:最后是使用 ctrl + d 退出 at 界面,保存 job
可以通过命令 atq
来查询有多少的 at job
:
powershell
[root@master ~]# atq
2 Sun Oct 6 14:35:00 2019 a root
4 Sun Oct 6 14:40:00 2019 a root
等到 14:40 分钟的时候,我们查看结果文件 /tmp/test.txt
,输出内容如下:
powershell
[root@master ~]# ls -al /tmp/test.txt
-rw-r--r--. 1 root root 2996 Oct 6 14:47 /tmp/test.txt
at
适合一次性的计划任务,执行完了就不会再执行了。
周期性计划任务
如果需要周期性的执行命令或者脚本的话,我们要用到 crontab
这个命令。
比如我们现在实现每分钟将当前的日期追加到文件 /tmp/date.txt
中。
执行 crontab -e
进行任务编辑界面,这个编辑界面和 vi 是一样的:
powershell
crontab -e
## 分钟 小时 日 月 星期 命令
* * * * * /usr/bin/date >> /tmp/date.txt
可以通过
which date
查看date
命令的全路径
以上就是每分钟会将 date
追加到 /tmp/date.txt
文件中。
可以通过
tail -fn300 /var/log/cron
来查看周期性任务的执行情况。
powershell
[root@master ~]# cat /tmp/date.txt
Sun Oct 6 17:29:02 CST 2019
Sun Oct 6 17:30:01 CST 2019
可以使用 crontab -l
来查看所有的周期性任务。
powershell
## 每个星期一的每分钟执行一次
* * * * 1 /usr/bin/date >> /tmp/date.txt
## 每个星期一或者星期五的每分钟执行一次
* * * * 1,5 /usr/bin/date >> /tmp/date.txt
## 每个星期一到星期五的每分钟执行一次
* * * * 1-5 /usr/bin/date >> /tmp/date.txt
## 7 月 7 号,并且属于周一到周五,每分钟执行一次
* * 7 7 1-5 /usr/bin/date >> /tmp/date.txt
## 每天凌晨 3 点 30 分执行一次
30 3 * * * /usr/bin/date >> /tmp/date.txt
## 每个星期一的凌晨 3 点 30 分执行一次
30 3 * * 1 /usr/bin/date >> /tmp/date.txt
## 每 15 分钟执行一次
15 * * * * /usr/bin/date >> /tmp/date.txt
文本操作
grep
在文件内容查找的时候,我们会使用 grep
命令来实现查找,比如:
- 从
/root/anaconda-ks.cfg
文件中,查找到单词password
所在的位置
powershell
[root@master ~]# grep password anaconda-ks.cfg
# Root password
[root@master ~]# grep -n password anaconda-ks.cfg
20:# Root password
-n
选项表示显示行号
如果我们想找到单词 pass
开头,后面有 4 个字符的字符串:
powershell
[root@master ~]# grep -n pass.... anaconda-ks.cfg
3:auth --enableshadow --passalgo=sha512
20:# Root password
[root@master ~]# grep -n pass....$ anaconda-ks.cfg
20:# Root password
.
表示匹配除换行符外的任意单个字符$
表示匹配一行的结尾*
表示匹配任意一个或者零个跟在它前面的字符
powershell
[root@master ~]# grep -n pas* anaconda-ks.cfg
3:auth --enableshadow --passalgo=sha512
20:# Root password
28:autopart --type=lvm
30:clearpart --none --initlabel
32:%packages
[root@master ~]# grep -n pass.* anaconda-ks.cfg
3:auth --enableshadow --passalgo=sha512
20:# Root password
[]
表示匹配方括号中字符类中的任意一个^
表示匹配开头
powershell
[root@master ~]# grep [Nn]etwork anaconda-ks.cfg
# Network information
network --bootproto=dhcp --device=ens33 --onboot=off --ipv6=auto --no-activate
network --hostname=localhost.localdomain
[root@master ~]# grep -i network anaconda-ks.cfg
# Network information
network --bootproto=dhcp --device=ens33 --onboot=off --ipv6=auto --no-activate
network --hostname=localhost.localdomain
[root@master ~]# grep ^# anaconda-ks.cfg
#version=DEVEL
# System authorization information
# Use CDROM installation media
# Use graphical install
# Run the Setup Agent on first boot
# Keyboard layouts
# System language
# Network information
# Root password
# System services
# System timezone
# System bootloader configuration
# Partition clearing information
现在要求查找 /root/anaconda-ks.cfg
文件中所有的 . 所在的行:
powershell
[root@master ~]# grep "\." anaconda-ks.cfg
lang en_US.UTF-8
network --hostname=localhost.localdomain
- 因为
.
是特殊符号,所以需要使用\
来转移,而且转义后不让.
再次成为匹配所有,所以需要加上双引号。
cut & sort
cut
cut
这个命令可以将一段信息的某一段切出来,处理数据的时候也是以行为单位。
比如,当我们输出 $PATH
的时候,它的取值是:
powershell
echo $PATH
## 输出是
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
## 现在我们想拿到上面的第 4 个字段
echo $PATH | cut -d ":" -f 4
## 输出为:
/usr/bin
## 现在想拿到第 3 个和第 5 个字段
echo $PATH | cut -d ":" -f 3,5
## 输出为:
/usr/sbin:/root/bin
选项参数的解释:
-d
表示分割字符,一般与-f
一起使用-f
:依据 -d 指定的分割字符将一段信息切割成数段,用-f
取出第几段
使用 cut
也可以指定字符区间来切割数据:
powershell
[root@localhost ~]# export
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/root"
declare -x HOSTNAME="localhost.localdomain"
declare -x LANG="en_US.UTF-8"
declare -x LESSOPEN="||/usr/bin/lesspipe.sh %s"
declare -x LOGNAME="root"
## 从第 12 个字符开始截取
[root@localhost ~]# export | cut -c 12-
HISTCONTROL="ignoredups"
HISTSIZE="1000"
HOME="/root"
HOSTNAME="localhost.localdomain"
LANG="en_US.UTF-8"
LESSOPEN="||/usr/bin/lesspipe.sh %s"
LOGNAME="root"
## 也可以指定范围进行切割,比如 cut -c 12-20
sort
sort
命令可以帮我们排序,而且可以依据不同的数据类型进行排序。
powershell
[root@localhost ~]# cat /etc/passwd | sort
adm:x:3:4:adm:/var/adm:/sbin/nologin
bigdata:x:1000:1000::/home/bigdata:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
## 按照第三个字段排序
[root@localhost ~]# cat /etc/passwd | sort -t ":" -k 3
root:x:0:0:root:/root:/bin/bash
bigdata:x:1000:1000::/home/bigdata:/bin/bash
user1:x:1001:1001::/home/user1:/bin/bash
user2:x:1002:1001::/home/user2:/bin/bash
user3:x:1003:1002::/home/user3:/bin/bash
## 反向排序
[root@localhost ~]# cat /etc/passwd | sort -t ":" -k 3 -r
nobody:x:99:99:Nobody:/:/sbin/nologin
polkitd:x:999:997:User for polkitd:/:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
find
前面使用 grep
是查找文件中的内容,如果我们想在指定的文件目录中查找指定的文件名的话,需要使用 find
命令,如下:
powershell
## 查找 /etc 目录下的 passwd 文件
[root@master ~]# find /etc -name passwd
/etc/passwd
/etc/pam.d/passwd
这样就可以找出 /etc
这个目录下的所有的名字为 passwd
的文件。
对于需要查找的文件名,我们也可以使用通配符
powershell
## 查找 /etc 目录下的所有的以 pass 开头的文件
[root@master ~]# find /etc -name pass*
/etc/openldap/certs/password
/etc/passwd
/etc/passwd-
/etc/pam.d/passwd
/etc/pam.d/password-auth-ac
/etc/pam.d/password-auth
/etc/selinux/targeted/active/modules/100/passenger
对于需要查找的文件名,我们也可以使用正则来匹配
powershell
## 查找 /etc 目录下的所有以 wd 结尾的文件
[root@master ~]# find /etc -regex .*wd$
/etc/passwd
/etc/pam.d/passwd
/etc/security/opasswd
sed
我们前面讲过 vi 和 vim 编辑器,这个编辑器主要是针对一个文件进行编辑的,而 sed
命令主要是针对文件中的每一行数据进行编辑的,包括:
- 以行为单位的新增/删除功能
- 以行为单位的替换/显示功能
以行为单位的新增/删除功能
- 将
/etc/passwd
的前十条数据列出并且打印行号,同时,请将 2~5 行删除:
powershell
[root@master ~]# nl /etc/passwd | head -10 | sed '2,5d'
1 root:x:0:0:root:/root:/bin/bash
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
## 删除第二行
[root@master ~]# nl /etc/passwd | head -10 | sed '2d'
1 root:x:0:0:root:/root:/bin/bash
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
## 删除第 3 行到最后一行,使用 $ 表示最后一行
[root@master ~]# nl /etc/passwd | head -10 | sed '3,$d'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
- 承上例,在第二行的后面加上一句话:how are you
powershell
[root@master ~]# nl /etc/passwd | head -5 | sed '2a how are you'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
how are you
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
## 新增两行
[root@master ~]# nl /etc/passwd | head -5 | sed '2a how are you \
> this '
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
how are you
this
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
## 在第二行前面加上一句话
[root@master ~]# nl /etc/passwd | head -5 | sed '2i how are you'
1 root:x:0:0:root:/root:/bin/bash
how are you
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
以行为单位的替换/显示功能
- 将第 2~5 行的内容替换为 "No 2-5 number"
powershell
[root@master ~]# nl /etc/passwd | head -10 | sed '2,5c No 2-5 number'
1 root:x:0:0:root:/root:/bin/bash
No 2-5 number
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
- 仅列出
/etc/passwd
文件内的第 5-7 行
powershell
[root@master ~]# nl /etc/passwd | head -7 | tail -3
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
[root@master ~]# nl /etc/passwd | sed -n '5,7p'
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
我们可以使用 sed
来替换文本,语法如下:
powershell
sed 's/要被替换的字符串/新的字符串/g'
- 从
ifconfig
中获取ip
的数据
powershell
## 获取网卡 ens33 的信息
[root@master ~]# ifconfig ens33
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.126.133 netmask 255.255.255.0 broadcast 192.168.126.255
inet6 fe80::6ddf:bb00:12ed:7ab9 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:60:74:d6 txqueuelen 1000 (Ethernet)
RX packets 4215 bytes 346199 (338.0 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1877 bytes 242074 (236.4 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
## 匹配到 ip 所在的行
[root@master ~]# ifconfig ens33 | grep 'inet '
inet 192.168.126.133 netmask 255.255.255.0 broadcast 192.168.126.255
## 替换 ip 前面的内容为空字符串
[root@master ~]# ifconfig ens33 | grep 'inet ' | sed 's/^.*inet //g'
192.168.126.133 netmask 255.255.255.0 broadcast 192.168.126.255
## 替换 ip 后面的内容为空字符串
[root@master ~]# ifconfig ens33 | grep 'inet ' | sed 's/^.*inet //g' | sed 's/ net.*$//g'
192.168.126.133
- 利用
sed
将/root/anaconda-ks.cfg
内每一行开头的#
替换为!
powershell
sed -i 's/^#/\!/g' anaconda-ks.cfg
sed -i 's/^!/\#/g' anaconda-ks.cfg
awk
上一篇文章中我们介绍使用 sed 来处理每行数据,如果要处理每一行中的每个字段的话,我们使用 awk 这个命令来完成。
我们现在查看 /etc/passwd
前面 5 行数据:
powershell
[root@master ~]# head -5 /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
可以看出以上每一行中的每一个字段都是使用 :
来分割开的,我们现在想显示每一行的第 1 个字段和第 4 个字段,我们可以:
powershell
[root@master ~]# head -5 /etc/passwd | awk -F ":" '{print $1,$4}'
root 0
bin 1
daemon 2
adm 4
lp 7
[root@master ~]# head -5 /etc/passwd | awk -F ":" '{print $0}'
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
可以看出:
-F
选项用于指定切分的字符$1、$2、$3...
分别表示第一个字段、第二个字段、第三个字段等等$0
表示一整行
我们也可以通过指定 FS
来指定切分的字符,如下:
powershell
[root@master ~]# head -5 /etc/passwd | awk 'BEGIN{FS=":"}{print $1,$4}'
root 0
bin 1
daemon 2
adm 4
lp 7
通过指定 OFS
来指定打印之后的字段的连接符:
powershell
[root@master ~]# head -5 /etc/passwd | awk 'BEGIN{FS=":";OFS="-"}{print $1,$4}'
root-0
bin-1
daemon-2
adm-4
lp-7
通过指定 RS
来指定记录的分隔符,如下:
powershell
## 指定 : 为行分隔符
[root@master ~]# head -5 /etc/passwd | awk 'BEGIN{RS=":"}{print $0}' | head -10
root
x
0
0
root
/root
/bin/bash
bin
x
1
指定 NR
来指定行号:
powershell
[root@master ~]# head -5 /etc/passwd | awk '{print NR,$0}'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
NF
表示字段的个数:
powershell
[root@master ~]# head -5 /etc/passwd | awk 'BEGIN{FS=":"}{print NF}'
7
7
7
7
7
[root@master ~]# head -5 /etc/passwd | awk 'BEGIN{FS=":"}{print $NF}'
/bin/bash
/sbin/nologin
/sbin/nologin
/sbin/nologin
/sbin/nologin
awk
也可以使用逻辑计算
powershell
[root@localhost ~]# ll | awk 'BEGIN{FN=" "}{print $1,$2,$3,$4}'
total 100
-rwxr--r--. 1 root root
-rwxr--r--. 1 root root
-rw-r--r--. 1 root root
-rw-r--r--. 1 root root
-rw-r--r--. 1 root root
-rw-r--r--. 1 root root
-rwxr--r--. 1 root root
## 过滤除文件大小大于等于 1259 字节的文件
[root@localhost ~]# ll | awk 'BEGIN{FN=" "} $5 >= 1259 {print $0}'
-rw-------. 1 root root 1259 Mar 19 2018 anaconda-ks.cfg
-rw-------. 1 root root 3010 Oct 9 17:20 nohup.out
-rw-r--r--. 1 root root 1547 Oct 9 17:24 test.out