shell基础/shell编程
2021-03-30 10:35:46 4 举报
AI智能生成
登录查看完整内容
linux shell详细学习资料整理
作者其他创作
大纲/内容
Linux基础知识-Part_8
Shell基础
一、Shell概述
1.什么是Shell
sh就是命令解释器
脚本就是命令的集合
2、shell的分类
主要语法类型有Bourne和C,彼此不兼容
Shell的两种主要语法类型有Bourne和C,这两种语法彼此不兼容。Bourne家族主要包括sh、ksh、Bash、psh、zsh;C家族主要包括:csh、tcsh (Bash和zsh在不同程度上支持csh 的语法)。
Bourne
sh
ksh
Bash
psh
zsh
C
csh
tcsh
Bash和zsh在不同程度上支持csh的语法
通过 /etc/shells文件来查询Linux支持的Shell
[root@localhost ~]# vi /etc/shells/bin/sh/bin/Bash/sbin/nologin/bin/tcsh/bin/csh
二、Shell脚本的执行方式
1、echo命令
[root@localhost ~]# echo [选项] [输出内容]选项:-e: 支持反斜线控制的字符转换(具体参见表11-2)-n: 取消输出后行末的换行符号(就是内容输出后不换行)
例子1:[root@localhost ~]# echo \"Mr. Shen Chao is the most honest man!\"#echo的内容就会打印到屏幕上。Mr. Shen Chao is the most honest man![root@localhost ~]#例子2:[root@localhost ~]# echo -n \"Mr. Shen Chao is the most honest man!\"Mr. Shen Chao is the most honest man![root@localhost ~]##如果加入了“-n”选项,输出内容结束后,不会换行直接显示新行的提示符。
在echo命令中如果使用了“-e”选项,则可以支持控制字符,如表11-2所示:
例子3:[root@localhost ~]# echo -e \"\\\\ \\a\"\\#这个输出会输出\\,同时会在系统音响中输出一声提示音例子4:[root@localhost ~]# echo -e \"ab\\bc\"ac#这个输出中,在b键左侧有“\\b”,所以输出时只有ac例子5:[root@localhost ~]# echo -e \"a\\tb\\tc\d\\te\\tf\
echo命令还可以进行一些比较有意思的东西,比如:
例子8:[root@localhost ~]# echo -e \"\\e[1;31m abcd \\e[0m\"
这条命令会把abcd按照红色输出。解释下这个命令\\e[1是标准格式,代表颜色输出开始,\\e[0m代表颜色输出结束,31m定义字体颜色是红色。echo能够识别的颜色如下:30m=黑色,31m=红色,32m=绿色,33m=黄色,34m=蓝色,35m=洋红,36m=青色,37m=白色。
例子9:[root@localhost ~]# echo -e \"\\e[1;42m abcd \\e[0m\"
这条命令会给abcd加入一个绿色的背景。echo可以使用的背景颜色如下:40m=黑色,41m=红色,42m=绿色,43m=黄色,44m=蓝色,45m=洋红,46m=青色,47m=白色。
echo -e 'abc! '感叹号后要加如空格
2、Shell脚本的执行
[root@localhost sh]# vi hello.sh#!/bin/Bash#The first program# Author: shenchao (E-mail: shenchao@atguigu.com)echo -e \"Mr. Shen Chao is the most honest man. \"
hell脚本写好了,那么这个脚本该如何运行呢?在Linux中脚本的执行主要有这样两种种方法:
赋予执行权限,直接运行
就是赋予执行权限之后,直接运行。当然运行时可以使用绝对路径,也可以使用相对路径运行。
[root@localhost sh]# chmod 755 hello.sh#赋予执行权限[root@localhost sh]# /root/sh/hello.shMr. Shen Chao is the most honest man.#使用绝对路径运行[root@localhost sh]# ./hello.shMr. Shen Chao is the most honest man.#因为我们已经在/root/sh目录当中,所以也可以使用相对路径运行
通过Bash调用执行脚本
[root@localhost sh]# bash hello.shMr. Shen Chao is the most honest man.
三、Bash的基本功能
1 历史命令
1)历史命令的查看
[root@localhost ~]# history [选项] [历史命令保存文件]选项:-c: 清空历史命令-w: 把缓存中的历史命令写入历史命令保存文件。如果不手工指定历史命令保存文件,则放入默认历史命令保存文件~/.bash_history中
设置history显示历史命令数量
[root@localhost ~]# vi /etc/profile…省略部分输出…HISTSIZE=1000…省略部分输出…
使用history命令查看的历史命令和~/.bash_history文件中保存的历史命令是不同的。那是因为当前登录操作的命令并没有直接写入~/.bash_history文件,而是保存在缓存当中的。需要等当前用户注销之后,缓存中的命令才会写入~/.bash_history文件。如果我们需要把内存中的命令直接写入~/.bash_history文件,而不等用户注销时再写入,就需要使用“-w”选项了。命令如下:
[root@localhost ~]# history -w#把缓存中的历史命令直接写入~/.bash_history
这时再去查询~/.bash_history文件,历史命令就和history命令查询的一致了。
如果需要清空历史命令,只需要执行:
[root@localhost ~]# history -c#清空历史命令
不建议使用
一般是在设置mysql密码时使用
2)、历史命令的调用
如果想要使用原先的历史命令有这样几种方法:
使用上、下箭头调用以前的历史命令 使用“!n”重复执行第n条历史命令 使用“!!”重复执行上一条命令 使用“!字串”重复执行最后一条以该字串开头的命令 使用“!$”重复上一条命令的最后一个参数
2、命令与文件的补全
Tab键
3 命令别名
命令格式:
命令格式:[root@localhost ~]# alias#查询命令别名[root@localhost ~]# alias 别名='原命令'#设定命令别名
例如:[root@localhost ~]# alias#查询系统中已经定义好的别名alias cp='cp -i'alias l.='ls -d .* --color=auto'alias ll='ls -l --color=auto'alias ls='ls --color=auto'alias mv='mv -i'alias rm='rm -i'alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'[root@localhost ~]# alias vi='vim'#定义vim命令的别名是vi
为了让这个别名永久生效,可以把别名写入环境变量配置文件“~/.bashrc”。命令如下:
[root@localhost ~]# vi /root/.bashrc
[root@localhost ~]# source /root/.bashrc
别名的优先级比命令高
命令执行时具体的顺序是什么
1、 第一顺位执行用绝对路径或相对路径执行的命令。2、 第二顺位执行别名。3、 第三顺位执行Bash的内部命令。4 、第四顺位执行按照$PATH环境变量定义的目录查找顺序找到的第一个命令。
4 Bash常用快捷键
ctrl+A
把光标移动到命令行开头。如果我们输入的命令过长,想要把光标移动到命令行开头时使用。
ctrl+E
把光标移动到命令行结尾。
ctrl+C
强制终止当前的命令。
ctrl+L
清屏,相当于clear命令。
ctrl+U
删除或剪切光标之前的命令。我输入了一行很长的命令,不用使用退格键一个一个字符的删除,使用这个快捷键会更加方便
ctrl+K
删除或剪切光标之后的内容。
ctrl+Y
粘贴ctrl+U或ctrl+K剪切的内容。
ctrl+R
在历史命令中搜索,按下ctrl+R之后,就会出现搜索界面,只要输入搜索内容,就会从历史命令中搜索。
ctrl+D
退出当前终端。
ctrl+Z
暂停,并放入后台。这个快捷键牵扯工作管理的内容,我们在系统管理章节详细介绍。
ctrl+S
暂停屏幕输出。
ctrl+Q
恢复屏幕输出。
5 输入输出重定向
1)、Bash的标准输入输出
键盘
/dev/stdin
0
标准输入
显示器
/dev/stdout
1
标准输出
/dev/stderr
2
标准错误输出
2)、输出重定向
标准输出重定向
命令 > 文件
命令 >> 文件
标准错误输出重定向
错误命令 2>文件
错误命令 2>>文件
正确输出和错误输出同时保存
命令 > 文件 2>&1
命令 >> 文件 2>&1
命令 &>文件
命令 &>>文件
命令>>文件1 2>>文件2
3)、输入重定向
[root@localhost ~]# wc [选项] [文件名]选项:-c 统计字节数-w 统计单词数-l 统计行数
6、多命令顺序执行
ls && echo yes || echo no
;
命令1 ;命令2
多个命令顺序执行,命令之间没有任何逻辑联系,即使其中一条命令报错,下面的命令依然会执行
&&
命令1 && 命令2
当命令1正确执行($?=0),则命令2才会执行当命令1执行不正确($?≠0),则命令2不会执行
||
命令1 || 命令2
当命令1 执行不正确($?≠0),则命令2才会执行当命令1正确执行($?=0),则命令2不会执行
7、 管道符
1)、行提取命令grep
[root@localhost ~]# grep [选项] \"搜索内容\" 文件名选项:-A 数字: 列出符合条件的行,并列出后续的n行-B 数字: 列出符合条件的行,并列出前面的n行-c: 统计找到的符合条件的字符串的次数-i: 忽略大小写-n: 输出行号-v: 反向查找--color=auto 搜索出的关键字用颜色显示
举几个例子:
[root@localhost ~]# grep \"/bin/bash\" /etc/passwd#查找用户信息文件/etc/passwd中,有多少可以登录的用户
再举几个例子吧:
[root@localhost ~]# grep -A 3 \"root\" /etc/passwd#查找包含有“root”的行,并列出后续的3行[root@localhost ~]# grep -n \"/bin/bash\" /etc/passwd#查找可以登录的用户,并显示行号[root@localhost ~]# grep -v \"/bin/bash\" /etc/passwd#查找不含有“/bin/bash”的行,其实就是列出所有的伪用户
2)find和grep的区别
find命令是在系统当中搜索符合条件的文件名,如果需要模糊查询,使用通配符(通配符我们下一小节进行介绍)进行匹配,搜索时文件名是完全匹配。
[root@localhost ~]# touch abc#建立文件abc[root@localhost ~]# touch abcd#建立文件abcd[root@localhost ~]# find . -name \"abc\"./abc#搜索文件名是abc的文件,只会找到abc文件,而不会找到文件abcd#虽然abcd文件名中包含abc,但是find是完全匹配,只能和要搜索的数据完全一样,才能找到
注意:find命令是可以通过-regex选项识别正则表达式规则的,也就是说find命令可以按照正则表达式规则匹配,而正则表达式是模糊匹配。但是对于初学者而言,find命令和grep命令本身就不好理解,所以我们这里只按照通配符规则来进行find查询。
grep命令是在文件当中搜索符合条件的字符串,如果需要模糊查询,使用正则表达式进行匹配,搜索时字符串是包含匹配。
[root@localhost ~]# echo abc > test#在test文件中写入abc数据[root@localhost ~]# echo abcd >> test#在test文件中再追加abcd数据[root@localhost ~]# grep \"abc\" testabcabcd#grep命令查找时,只要数据行中包含有abc,就会都列出#所以abc和abcd都可以查询到
3)管道符
[root@localhost ~]# ll -a /etc/ | more
[root@localhost ~]# netstat -an | grep \"ESTABLISHED\"#查询下本地所有网络连接,提取包含ESTABLISHED(已建立连接)的行#就可以知道我们的服务器上有多少已经成功连接的网络连接
[root@localhost ~]# netstat -an | grep \"ESTABLISHED\" | wc -l#如果想知道具体的网络连接数量,就可以再使用wc命令统计行数
[root@localhost ~]# rpm -qa | grep httpd
8 通配符
?
匹配一个任意字符
*
匹配0个或任意多个任意字符,也就是可以匹配任何内容
[]
匹配中括号中任意一个字符。例如:[abc]代表一定匹配一个字符,或者是a,或者是b,或者是c。
[-]
匹配中括号中任意一个字符,-代表一个范围。例如:[a-z]代表匹配一个小写字母。
[^]
逻辑非,表示匹配不是中括号内的一个字符。例如:[^0-9]代表匹配一个不是数字的字符。
[root@localhost tmp]# touch abc[root@localhost tmp]# touch abcd[root@localhost tmp]# touch 012[root@localhost tmp]# touch 0abc
#建立几个测试文件[root@localhost tmp]# ls *012 0abc abc abcd#“*”代表所有的文件[root@localhost tmp]# ls ?abc0abc#“?”匹配任意一个字符,所以会匹配0abc#但是不能匹配abc,因为“?”不能匹配空[root@localhost tmp]# ls [0-9]*012 0abc#匹配任何以数字开头的文件[root@localhost tmp]# ls [^0-9]*abc abcd#匹配不已数字开头的文件
9 Bash中其他特殊符号
''
单引号。在单引号中所有的特殊符号,如“$”和“`”(反引号)都没有特殊含义。
\"\"
双引号。在双引号中特殊符号都没有特殊含义,但是“$”、“`”和“\\”是例外,拥有“调用变量的值”、“引用命令”和“转义符”的特殊含义。
``
反引号。反引号括起来的内容是系统命令,在Bash中会先执行它。和$()作用一样,不过推荐使用$(),因为反引号非常容易看错。
$()
和反引号作用一样,用来引用系统命令。
()
用于一串命令执行时,()中的命令会在子Shell中运行
{}
用于一串命令执行时,{}中的命令会在当前Shell中执行。也可以用于变量变形与替换。
用于变量的测试。
#
在Shell脚本中,#开头的行代表注释。
$
用于调用变量的值,如需要调用变量name的值时,需要用$name的方式得到变量的值。
\\
转义符,跟在\\之后的特殊符号将失去特殊含义,变为普通字符。如\\$将输出“$”符号,而不当做是变量引用。
[root@localhost ~]# name=sc#定义变量name的值是sc(就是最正直的人,超哥我了!)[root@localhost ~]# echo '$name'$name#如果输出时使用单引号,则$name原封不动的输出[root@localhost ~]# echo \"$name\"sc#如果输出时使用双引号,则会输出变量name的值sc[root@localhost ~]# echo `date`2018年 10月 21日 星期一 18:16:33 CST#反引号括起来的命令会正常执行[root@localhost ~]# echo '`date`'`date`#但是如果反引号命令被单引号括起来,那么这个命令不会执行,`date`会被当成普通字符输出[root@localhost ~]# echo \"`date`\"2018年 10月 21日 星期一 18:14:21 CST#如果是双引号括起来,那么这个命令又会正常执行
[root@localhost ~]# echo lsls#如果命令不用反引号包含,命令不会执行,而是直接输出[root@localhost ~]# echo `ls`anaconda-ks.cfg install.log install.log.syslog sh test testfile#只有用反引号包括命令,这个命令才会执行[root@localhost ~]# echo $(date)2018年 10月 21日 星期一 18:25:09 CST#使用$(命令)的方式也是可以的
在介绍小括号和大括号的区别之前,我们先要解释一个概念,那就是父Shell和子Shell。在我们的Bash中,是可以调用新的Bash的,比如:
[root@localhost ~]# bash#进入子shell中[root@localhost ~]#
通过pstree命令查看一下进程数:
[root@localhost ~]# pstreeinit─┬─abrt-dump-oops…省略部分输出├─sshd─┬─sshd───bash(父)───bash(子)───pstree
知道了父Shell和子Shell,我们接着解释小括号和大括号的区别。如果是用于一串命令的执行,那么小括号和大括号的主要区别在于:
()执行一串命令时,需要重新开一个子shell进行执行 {}执行一串命令时,是在当前shell执行; ()和{}都是把一串的命令放在括号里面,并且命令之间用;号隔开; ()最后一个命令可以不用分号; {}最后一个命令要用分号; {}的第一个命令和左括号之间必须要有一个空格; ()里的各命令不必和括号有空格; ()和{}中括号里面的某个命令的重定向只影响该命令,但括号外的重定向则影响到括号里的所有命令。
例子
[root@localhost ~]# name=sc#在父Shell中定义变量name的值是sc[root@localhost ~]# (name=liming;echo $name)liming#如果用()括起来一串命令,这些命令都可以执行#给name变量重新赋值,但是这个值只在子Shell中生效[root@localhost ~]# echo $namesc#父Shell中name的值还是sc,而不是liming[root@localhost ~]# { name=liming;echo $name; }liming#但是用大括号来进行一串命令的执行时,name变量的修改是直接在父Shell当中的#注意大括号的格式[root@localhost ~]# echo $nameliming#所以name变量的值已经被修改了
四、Bash的变量和运算符
1 什么是变量
在定义变量时,有一些规则需要遵守:
变量名称可以由字母、数字和下划线组成,但是不能以数字开头。如果变量名是“2name”则是错误的。
在Bash中,变量的默认类型都是字符串型,如果要进行数值运算,则必修指定变量类型为数值型。
变量用等号连接值,等号左右两侧不能有空格。
变量的值如果有空格,需要使用单引号或双引号包括。如:“test=\"hello world!\"”。其中双引号括起来的内容“$”、“\\”和反引号都拥有特殊含义,而单引号括起来的内容都是普通字符。
在变量的值中,可以使用“\\”转义符。
如果需要增加变量的值,那么可以进行变量值的叠加。不过变量需要用双引号包含\"$变量名\"或用${变量名}包含变量名。例如:
[root@localhost ~]# test=123[root@localhost ~]# test=\"$test\"456[root@localhost ~]# echo $test123456#叠加变量test,变量值变成了123456[root@localhost ~]# test=${test}789[root@localhost ~]# echo $test123456789#再叠加变量test,变量值编程了123456789
变量值的叠加可以使用两种格式:“$变量名”或${变量名}
如果是把命令的结果作为变量值赋予变量,则需要使用反引号或$()包含命令。例如:
[root@localhost ~]# test=$(date)[root@localhost ~]# echo $test2018年 10月 21日 星期一 20:27:50 CST
环境变量名建议大写,便于区分。
2、变量的分类
用户自定义变量
这种变量是最常见的变量,由用户自由定义变量名和变量的值。
环境变量
这种变量中主要保存的是和系统操作环境相关的数据,比如当前登录用户,用户的家目录,命令的提示符等。不是太好理解吧,那么大家还记得在Windows中,同一台电脑可以有多个用户登录,而且每个用户都可以定义自己的桌面样式和分辨率,这些其实就是Windows的操作环境,可以当做是Windows的环境变量来理解。环境变量的变量名可以自由定义,但是一般对系统起作用的环境变量的变量名是系统预先设定好的。
位置参数变量
这种变量主要是用来向脚本当中传递参数或数据的,变量名不能自定义,变量作用是固定的。
预定义变量
是Bash中已经定义好的变量,变量名不能自定义,变量作用也是固定的。
3、用户自定义变量
1)、变量定义
[root@localhost ~]# 2name=\"shen chao\"-bash: 2name=shen chao: command not found#变量名不能用数字开头[root@localhost ~]# name = \"shenchao\"-bash: name: command not found#等号左右两侧不能有空格[root@localhost ~]# name=shen chao-bash: chao: command not found#变量的值如果有空格,必须用引号包含
2)、 变量调用
[root@localhost ~]# name=\"shen chao\"#定义变量name[root@localhost ~]# echo $nameshen chao#输出变量name的值
3)、 变量查看
[root@localhost ~]# set [选项]选项:-u: 如果设定此选项,调用未声明变量时会报错(默认无任何提示)-x: 如果设定此选项,在命令执行之前,会把命令先输出一次
[root@localhost ~]# setBASH=/bin/bash…省略部分输出…name='shen chao'#直接使用set命令,会查询系统中所有的变量,包含用户自定义变量和环境变量[root@localhost ~]# set -u[root@localhost ~]# echo $file-bash: file: unbound variable#当设置了-u选项后,如果调用没有设定的变量会有报错。默认是没有任何输出的。[root@localhost ~]# set -x[root@localhost ~]# ls+ ls --color=autoanaconda-ks.cfg install.log install.log.syslog sh tdir test testfile#如果设定了-x选项,会在每个命令执行之前,先把命令输出一次
-u 参数可以区分变量是否为空还是不存在的情况
这个命令是临时生效的,永久生效需要写入配置文件
4)、 变量删除
[root@localhost ~]# unset 变量名
4 环境变量
1)、环境变量设置
[root@localhost ~]# export age=\"18\"#使用export声明的变量即是环境变量[root@localhost ~]# age=\"18\"[root@localhost ~]# export age
2)、环境变量查询和删除
env命令和set命令的区别是,set命令可以查看所有变量,而env命令只能查看环境变量。
[root@localhost ~]# unset gender[root@localhost ~]# env | grep gender#删除环境变量gender
3)、系统默认环境变量 env
[root@localhost ~]# envHOSTNAME=localhost.localdomain 主机名SHELL=/bin/bash 当前的shellTERM=linux 终端环境HISTSIZE=1000 历史命令条数SSH_CLIENT=192.168.4.159 4824 22 当前操作环境是用ssh连接的,这里记录客户端ipSSH_TTY=/dev/pts/1 ssh连接的终端时pts/1USER=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:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.tbz=01;31:*.tbz2=01;31:*.bz=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=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:*.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: 定义颜色显示age=18 我们刚刚定义的环境变量PATH=/usr/lib/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin 系统查找命令的路径MAIL=/var/spool/mail/root 用户邮箱PWD=/root 当前所在目录LANG=zh_CN.UTF-8 语系HOME=/root 当前登录用户的家目录SHLVL=2 当前在第二层子shell中。还记得我们刚刚进入了一个子shell吗?如果是第一层shell,这里是1LOGNAME=root 登录用户_=/bin/env 上次执行命令的最后一个参数或命令本身
nv命令可以查询到所有的环境变量,可是还有一些变量虽然不是环境变量,却是和Bash操作接口相关的变量,这些变量也对我们的Bash操作终端起到了重要的作用。这些变量就只能用set命令来查看了,我只列出重要的内容吧:
[root@localhost ~]# setBASH=/bin/bash Bash的位置BASH_VERSINFO=([0]=\"4\" [1]=\"1\" [2]=\"2\" [3]=\"1\" [4]=\"release\"[5]=\"i386-redhat-linux-gnu\") Bash版本BASH_VERSION='4.1.2(1)-release' bash的版本COLORS=/etc/DIR_COLORS 颜色记录文件HISTFILE=/root/.bash_history 历史命令保存文件HISTFILESIZE=1000 在文件当中记录的历史命令最大条数HISTSIZE=1000 在缓存中记录的历史命令最大条数LANG=zh_CN.UTF-8 语系环境MACHTYPE=i386-redhat-linux-gnu 软件类型是i386兼容类型MAILCHECK=60 每60秒去扫描新邮件PPID=2166 父shell的PID。我们当前Shell是一个子shellPS1='[\\u@\\h \\W]\\$ ' 命令提示符PS2='> ' 如果命令一行没有输入完成,第二行命令的提示符UID=0 当前用户的UID
PATH变量:系统查找命令的路径
先查询下PATH环境变量的值:
[root@localhost ~]# echo $PATH/usr/lib/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
PATH变量的值是用“:”分割的路径,这些路径就是系统查找命令的路径。也就是说当我们输入了一个程序名,如果没有写入路径,系统就会到PATH变量定义的路径中去寻找,是否有可以执行的程序。如果找到则执行,否则会报“命令没有发现”的错误。
脚本也可以不输入路径而直接运行
[root@localhost ~]# cp /root/sh/hello.sh /bin/#拷贝hello.sh到/bin目录[root@localhost ~]# hello.shMr. Shen Chao is the most honest man.#hello.sh可以直接执行了
修改PATH变量的值
[root@localhost ~]# PATH=\"$PATH\":/root/sh#在变量PATH的后面,加入/root/sh目录[root@localhost ~]# echo $PATH/usr/lib/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/root/sh#查询PATH的值,变量叠加生效了
当然我们这样定义的PATH变量只是临时生效,一旦重启或注销就会消失,如果想要永久生效,需要写入环境变量配置文件
PS1变量:命令提示符设置
PS1是一个很有意思的变量(这可不是SONY的游戏机哦),是用来定义命令行的提示符的,可以安装我们自己的需求来定义自己喜欢的提示符。PS1可以支持以下这些选项:
选项
\\d:显示日期,格式为“星期 月 日” \\H:显示完整的主机名。如默认主机名“localhost.localdomain” \\h:显示简写主机名。如默认主机名“localhost” \\t:显示24小时制时间,格式为“HH:MM:SS” \\T:显示12小时制时间,格式为“HH:MM:SS” \\A:显示24小时制时间,格式为“HH:MM” \\@:显示12小时制时间,格式为“HH:MM am/pm” \\u:显示当前用户名 \\v:显示Bash的版本信息 \\w:显示当前所在目录的完整名称 \\W:显示当前所在目录的最后一个目录 \\#:执行的第几个命令 \\$:提示符。如果是root用户会显示提示符为“#”,如果是普通用户会显示提示符为“$”
[root@localhost ~]# echo $PS1[\\u@\\h \\W]\\$#默认的提示符是显示“[用户名@简写主机名 最后所在目录]提示符”
[root@localhost ~]# PS1='[\\u@\\t \\w]\\$ '#修改提示符为‘[用户名@当前时间 当前所在完整目录]提示符’[root@04:46:40 ~]#cd /usr/local/src/#切换下当前所在目录,因为家目录是看不出来区别的[root@04:47:29 /usr/local/src]##看到了吗?提示符按照我们的设计发生了变化
这里要小心,PS1变量的值要用单引号包含,否则设置不生效。
[root@04:50:08 /usr/local/src]#PS1='[\\u@\\@ \\h \\# \\W]\\$'[root@04:53 上午 localhost 31 src]##提示符又变了。\\@:时间格式是HH:MM am/pm;\\#:会显示执行了多少个命令
LANG语系变量
LANG变量定义了Linux系统的主语系环境,这个变量的默认值是:
[root@localhost src]# echo $LANGzh_CN.UTF-8
Linux中到底支持多少语系呢?
[root@localhost src]# locale -a | moreaa_DJaa_DJ.iso88591aa_DJ.utf8aa_ER…省略部分输出…#查询支持的语系[root@localhost src]# locale -a | wc -l735#是在太多了,统计一下有多少个吧
当前系统到底是什么语系呢?使用locale命令直接查询:
[root@localhost src]# localeLANG=zh_CN.UTF-8LC_CTYPE=\"zh_CN.UTF-8\"
LC_NUMERIC=\"zh_CN.UTF-8\"LC_TIME=\"zh_CN.UTF-8\"LC_COLLATE=\"zh_CN.UTF-8\"LC_MONETARY=\"zh_CN.UTF-8\"LC_MESSAGES=\"zh_CN.UTF-8\"LC_PAPER=\"zh_CN.UTF-8\"LC_NAME=\"zh_CN.UTF-8\"LC_ADDRESS=\"zh_CN.UTF-8\"LC_TELEPHONE=\"zh_CN.UTF-8\"LC_MEASUREMENT=\"zh_CN.UTF-8\"LC_IDENTIFICATION=\"zh_CN.UTF-8\"LC_ALL=
我们还要通过文件/etc/sysconfig/i18n定义系统的默认语系,查看下这个文件的内容:
[root@localhost src]# cat /etc/sysconfig/i18nLANG=\"zh_CN.UTF-8\"
这又是当前系统语系,又是默认语系,有没有快晕倒的感觉。解释下吧,我们可以这样理解,默认语系是下次重启之后系统所使用的语系,而当前系统语系是当前系统使用的语系。如果系统重启,会从默认语系配置文件/etc/sysconfig/i18n中读出语系,然后赋予变量LANG让这个语系生效。也就是说,LANG定义的语系只对当前系统生效,要想永久生效就要修改/etc/sysconfig/i18n文件了。
说到这里,我们需要解释下Linux中文支持的问题。是不是我们只要定义了语系为中文语系,如zh_CN.UTF-8就可以正确显示中文了呢?这要分情况,如果我们是在图形界面中,或者是使用远程连接工具(如SecureCRT),只要正确设置了语系,那么是可以正确显示中文的。当然远程连接工具也要配置正确的语系环境,具体配置方式可以参考Linux系统安装章节。
那么如果是纯字符界面(本地终端tty1-tty6)是不能显示中文的,因为Linux的纯字符界面时不能显示中文这么复杂的编码的。如果我们非要在纯字符界面显示中文,那么只能安装中文插件,如zhcon等。我们举个例子吧:
[root@localhost src]# echo $LANGzh_CN.UTF-8#我当前使用远程工具连接,只要语系正确,则可以正确显示中文[root@localhost src]# df文件系统 1K-块 已用 可用 已用% 挂载点/dev/sda3 19923216 1813532 17097616 10% /tmpfs 312672 0 312672 0% /dev/shm/dev/sda1 198337 26359 161738 15% /boot#df命令可以看到中文是正常的
但如果是纯字符界面呢?虽然我们是中文安装的,但纯字符界面的语系可是“en_US.UTF-8”,如图11-2所示:
那么我们更改下语系为中文,看看会出现什么情况吧:
如果我们非要在纯字符界面中设置中文语系,那么就会出现乱码。怎么解决呢?安装zhcon中文插件吧,安装并不复杂,查询下安装说明应该可以轻松安装。
5 位置参数变量
$n
n为数字,$0代表命令本身,$1-$9代表第一到第九个参数,十以上的参数需要用大括号包含,如${10}.
$*
这个变量代表命令行中所有的参数,$*把所有的参数看成一个整体
$@
这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待
$#
这个变量代表命令行中所有参数的个数
[root@localhost sh]# vi count.sh#!/bin/bash# Author: shenchao (E-mail: shenchao@atguigu.com)num1=$1#给num1变量赋值是第一个参数num2=$2#给num2变量赋值是第二个参数sum=$(( $num1 + $num2))#变量sum的和是num1加num2#Shell当中的运算还是不太一样的,我们Shell运算符小节中详细介绍echo $sum#打印变量sum的值
[root@localhost sh]# vi parameter.sh#!/bin/bash# Author: shenchao (E-mail: shenchao@atguigu.com)echo \"A total of $# parameters\"#使用$#代表所有参数的个数echo \"The parameters is: $*\"#使用$*代表所有的参数echo \"The parameters is: $@\"#使用$@也代表所有参数
“$*”和“$@”有区别吗
$*会把接收的所有参数当成一个整体对待,而$@则会区分对待接收到的所有参数。
[root@localhost sh]# vi parameter2.sh#!/bin/bash# Author: shenchao (E-mail: shenchao@atguigu.com)for i in \"$*\"#定义for循环,in后面有几个值,for会循环多少次,注意“$*”要用双引号括起来#每次循环会把in后面的值赋予变量i#Shell把$*中的所有参数看成是一个整体,所以这个for循环只会循环一次doecho \"The parameters is: $i\"#打印变量$i的值donex=1#定义变量x的值为1for y in \"$@\"#同样in后面的有几个值,for循环几次,每次都把值赋予变量y#可是Shell中把“$@”中的每个参数都看成是独立的,所以“$@”中有几个参数,就会循环几次doecho \"The parameter$x is: $y\"#输出变量y的值x=$(( $x +1 ))#然变量x每次循环都加1,为了输出时看的更清楚done
6 预定义变量
$?
最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。
$$
当前进程的进程号(PID)
$!
后台运行的最后一个进程的进程号(PID)
$?
[root@localhost sh]# lscount.sh hello.sh parameter2.sh parameter.sh#ls命令正确执行[root@localhost sh]# echo $?0#预定义变量“$?”的值是0,证明上一个命令执行正确[root@localhost sh]# ls install.logls: 无法访问install.log: 没有那个文件或目录#当前目录中没有install.log文件,所以ls命令报错了[root@localhost sh]# echo $?2#变量“$?”返回一个非0的值,证明上一个命令没有正确执行#至于错误的返回值到底是多少,是在编写ls命令时定义好的,如果碰到文件不存在就返回数值2
“$$”和“$!”
[root@localhost sh]# vi variable.sh#!/bin/bash# Author: shenchao (E-mail: shenchao@atguigu.com)echo \"The current process is $$\"#输出当前进程的PID。#这个PID就是variable.sh这个脚本执行时,生成的进程的PIDfind /root -name hello.sh 使用find命令在root目录下查找hello.sh文件#符号&的意思是把命令放入后台执行,工作管理我们在系统管理章节会详细介绍echo \"The last one Daemon process is $!\"#输出这个后台执行命令的进程的PID,也就是输出find命令的PID号
7 接收键盘输入
[root@localhost ~]# read [选项] [变量名]选项:-p “提示信息”: 在等待read输入时,输出提示信息-t 秒数: read命令会一直等待用户输入,使用此选项可以指定等待时间-n 字符数: read命令只接受指定的字符数,就会执行-s: 隐藏输入的数据,适用于机密信息的输入变量名:变量名可以自定义,如果不指定变量名,会把输入保存入默认变量REPLY如果只提供了一个变量名,则整个输入行赋予该变量如果提供了一个以上的变量名,则输入行分为若干字,一个接一个地赋予各个变量,而命令行上的最后一个变量取得剩余的所有字
[root@localhost sh]# vi read.sh#!/bin/bash# Author: shenchao (E-mail: shenchao@atguigu.com)read -t 30 -p \"Please input your name: \" name#提示“请输入姓名”并等待30秒,把用户的输入保存入变量name中echo \"Name is $name\"#看看变量“$name”中是否保存了你的输入read -s -t 30 -p \"Please enter your age: \" age#提示“请输入年龄”并等待30秒,把用户的输入保存入变量age中#年龄是隐私,所以我们用“-s”选项隐藏输入echo -e \"\\"#调整输出格式,如果不输出换行,一会的年龄输出不会换行echo \"Age is $age\"read -n 1 -t 30 -p \"Please select your gender[M/F]: \" gender#提示“请选择性别”并等待30秒,把用户的输入保存入变量gender#使用“-n 1”选项只接收一个输入字符就会执行(都不用输入回车)echo -e \"\\"echo \"Sex is $gender\"
8 Shell的运算符
1)数值运算的方法
可以采用以下三种方法中的任意一种:
使用declare声明变量类型
[root@localhost ~]# declare [+/-][选项] 变量名选项:-: 给变量设定类型属性+: 取消变量的类型属性-a: 将变量声明为数组型-i: 将变量声明为整数型(integer)-r: 讲变量声明为只读变量。注意,一旦设置为只读变量,既不能修改变量的值,也不能删除变量,甚至不能通过+r取消只读属性-x: 将变量声明为环境变量-p: 显示指定变量的被声明的类型
[root@localhost ~]# aa=11[root@localhost ~]# bb=22#给变量aa和bb赋值[root@localhost ~]# declare -i cc=$aa+$bb#声明变量cc的类型是整数型,它的值是aa和bb的和[root@localhost ~]# echo $cc33#这下终于可以相加了
例子2:数组变量类型
[root@localhost ~]# name[0]=\"shen chao\"#数组中第一个变量是沈超(大办公室第一个办公桌坐最高大威猛帅气的人)[root@localhost ~]# name[1]=\"li ming\"#数组第二个变量是李明(大办公室第二个办公桌坐头发锃亮的人)[root@localhost ~]# name[2]=\"tong gang\"#数组第三个变量是佟刚(大办公室第三个办公桌坐眼睛比超哥还小的老师)[root@localhost ~]# echo ${name}shen chao#输出数组的内容,如果只写数组名,那么只会输出第一个下标变量[root@localhost ~]# echo ${name[*]}shen chao li ming tong gang#输出数组所有的内容
采用了“变量名[下标]”的格式,这个变量就会被系统认为是数组型了,不用强制声明。
例子3: 环境变量
[root@localhost ~]# declare -x test=123#把变量test声明为环境变量
也可以使用declare命令把变量声明为环境变量,和export命令的作用是一样的:
例子4:只读属性
注意一旦给变量设定了只读属性,那么这个变量既不能修改变量的值,也不能删除变量,甚至不能使用“+r”选项取消只读属性。
[root@localhost ~]# declare -r test#给test赋予只读属性[root@localhost ~]# test=456-bash: test: readonly variable#test变量的值就不能修改了[root@localhost ~]# declare +r test-bash: declare: test: readonly variable#也不能取消只读属性[root@localhost ~]# unset test-bash: unset: test: cannot unset: readonly variable#也不能删除变量
不过还好这个变量只是命令行声明的,所以只要重新登录或重启,这个变量就会消失了。
例子5:查询变量属性和取消变量属性
变量属性的查询使用“-p”选项,变量属性的取消使用“+”选项。命令如下:
[root@localhost ~]# declare -p ccdeclare -i cc=\"33\"#cc变量是int型[root@localhost ~]# declare -p namedeclare -a name='([0]=\"shen chao\" [1]=\"li ming\" [2]=\"tong gang\")'#name变量是数组型[root@localhost ~]# declare -p testdeclare -rx test=\"123\"#test变量是环境变量和只读变量[root@localhost ~]# declare +x test#取消test变量的环境变量属性[root@localhost ~]# declare -p testdeclare -r test=\"123\"#注意,只读变量属性是不能取消的
使用expr或let数值运算工具
要想进行数值运算的第二种方法是使用expr命令,这种命令就没有declare命令复杂了。
[root@localhost ~]# aa=11[root@localhost ~]# bb=22#给变量aa和变量bb赋值[root@localhost ~]# dd=$(expr $aa + $bb)#dd的值是aa和bb的和。注意“+”号左右两侧必须有空格[root@localhost ~]# echo $dd33
至于let命令和expr命令基本类似,都是Linux中的运算命令,命令格式如下:
[root@localhost ~]# aa=11[root@localhost ~]# bb=22#给变量aa和变量bb赋值[root@localhost ~]# let ee=$aa+$bb[root@localhost ~]# echo $ee33#变量ee的值是aa和bb的和[root@localhost ~]# n=20#定义变量n[root@localhost ~]# let n+=1#变量n的值等于变量本身再加1[root@localhost ~]# echo $n21
使用“$((运算式))”或“$[运算式]”方式运算
其实这是一种方式“$(())”和“$[]”这两种括号按照个人习惯使用即可。
推荐使用“$((运算式))”的方式
[root@localhost ~]# aa=11[root@localhost ~]# bb=22[root@localhost ~]# ff=$(( $aa+$bb ))[root@localhost ~]# echo $ff33#变量ff的值是aa和bb的和[root@localhost ~]# gg=$[ $aa+$bb ][root@localhost ~]# echo $gg33#变量gg的值是aa和bb的和
2)、 Shell常用运算符
13
单目负、单目正
12
逻辑非、按位取反或补码
11
乘、除、取模
10
加、减
9
按位左移、按位右移
8
小于或等于、大于或等于、小于、大于
7
等于、不等于
6
&
按位与
5
^
按位异或
4
|
按位或
3
逻辑与
逻辑或
赋值、运算且赋值
例子1: 加减乘除
[root@localhost ~]# aa=$(( (11+3)*3/2 ))#虽然乘和除的优先级高于加,但是通过小括号可以调整运算优先级[root@localhost ~]# echo $aa21
例子2:取模运算
[root@localhost ~]# bb=$(( 14%3 ))[root@localhost ~]# echo $bb2#14不能被3整除,余数是2
例子3:逻辑与
[root@localhost ~]# cc=$(( 1 && 0 ))[root@localhost ~]# echo $cc0#逻辑与运算只有想与的两边都是1,与的结果才是1,否则与的结果是0
运算符优先级表明在每个表达式或子表达式中哪一个运算对象首先被求值,数值越大优先级越高,具有较高优先级级别的运算符先于较低级别的运算符进行求值运算。
9 变量的测试与内容置换
如果大括号内没有“:”,则变量y是为空,还是没有设置,处理方法是不同的;如果大括号内有“:”,则变量y不论是为空,还是没有没有设置,处理方法是一样的。
如果大括号内是“-”或“+”,则在改变变量x值的时候,变量y是不改变的;如果大括号内是“=”,则在改变变量x值的同时,变量y的值也会改变。
如果大括号内是“?”,则当变量y不存在或为空时,会把“新值”当成报错输出到屏幕上。
例子1:
[root@localhost ~]# unset y#删除变量y[root@localhost ~]# x=${y-new}#进行测试[root@localhost ~]# echo $xnew#因为变量y不存在,所以x=new[root@localhost ~]# echo $y#但是变量y还是不存在的
[root@localhost ~]# y=\"\"#给变量y赋值为空[root@localhost ~]# x=${y-new}#进行测试[root@localhost ~]# echo $x[root@localhost ~]# echo $y#变量x和变量y值都是空
[root@localhost ~]# y=old#给变量y赋值[root@localhost ~]# x=${y-new}#进行测试[root@localhost ~]# echo $xold[root@localhost ~]# echo $yold#变量x和变量y的值都是old
例子2:
那如果大括号内是“=”号,又该是什么情况呢?先测试下变量y没有设置的情况:
[root@localhost ~]# unset y#删除变量y[root@localhost ~]# x=${y:=new}#进行测试[root@localhost ~]# echo $xnew[root@localhost ~]# echo $ynew#变量x和变量y的值都是new
一旦使用“=”号,那么变量y和变量x都会同时进行处理,而不像例子1中只改变变量x的值。那如果变量y为空又是什么情况呢?
[root@localhost ~]# y=\"\"#设定变量y为空[root@localhost ~]# x=${y:=new}#进程测试[root@localhost ~]# echo $xnew[root@localhost ~]# echo $ynew#变量x和变量y的值都是new
一旦在大括号中使用“:”,那么变量y为空或者不设定,处理方式都是一样的了。那如果y已经赋值了,又是什么情况:
[root@localhost ~]# y=old #给y赋值[root@localhost ~]# x=${y:=new}#进行测试[root@localhost ~]# echo $xold[root@localhost ~]# echo $yold#原来变量x和变量y的值都是old
例子3:
再测试下大括号中是“?”的情况吧:
[root@localhost ~]# unset y#删除变量y[root@localhost ~]# x=${y?new}-bash: y: new#会把值“new”输出到屏幕上
那如果变量y已经赋值了呢:
[root@localhost ~]# y=old#给变量y赋值[root@localhost ~]# x=${y?new}#进行测试[root@localhost ~]# echo $xold[root@localhost ~]# echo $yold#变量x和变量y的值都是old
五 环境变量配置文件
1 source命令
[root@localhost ~]# source 配置文件或[root@localhost ~]# . 配置文件
2 环境变量配置文件
1)、 登录时生效的环境变量配置文件
在Linux系统登录时主要生效的环境变量配置文件有以下五个:
对所有用户都生效
对当前用户生效
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
~/.bash_profile
~/.bashrc
环境变量配置文件调用过程
在用户登录过程先调用/etc/profile文件
在这个环境变量配置文件中会定义这些默认环境变量:
USER变量:根据登录的用户,给这个变量赋值(就是让USER变量的值是当前用户)。
LOGNAME变量:根据USER变量的值,给这个变量赋值。
MAIL变量:根据登录的用户,定义用户的邮箱为/var/spool/mail/用户名。
PATH变量:根据登录用户的UID是否为0,判断PATH变量是否包含/sbin、/usr/sbin和/usr/local/sbin这三个系统命令目录。
HOSTNAME变量:更加主机名,给这个变量赋值。
HISTSIZE变量:定义历史命令的保存条数。
umask:定义umask默认权限。注意/etc/profile文件中的umask权限是在“有用户登录过程(也就是输入了用户名和密码)”时才会生效。
调用/etc/profile.d/*.sh文件,也就是调用/etc/profile.d/目录下所有以.sh结尾的文件。
由/etc/profile文件调用/etc/profile.d/*.sh文件
这个目录中所有以.sh结尾的文件都会被/etc/profile文件调用,这里最常用的就是lang.sh文件,而这个文件又会调用/etc/sysconfig/i18n文件。/etc/sysconfig/i18n这个文件眼熟吗?就是我们前面讲过的默认语系配置文件啊。
由/etc/profile文件调用~/.bash_profile文件
~/.bash_profile文件就没有那么复杂了,这个文件主要实现了两个功能:
调用了~/.bashrc文件。
在PATH变量后面加入了“:$HOME/bin”这个目录。那也就是说,如果我们在自己的家目录中建立bin目录,然后把自己的脚本放入“~/bin”目录,就可以直接执行脚本,而不用通过目录执行了。
由~/.bash_profile文件调用~/.bashrc文件
在~/.bashrc文件中主要实现了:
定义默认别名,所以超哥把自己定义的别名也放入了这个文件。 调用/etc/bashrc
由~/.bashrc调用了/etc/bashrc文件
在/etc/bashrc文件中主要定义了这些内容:
PS1变量:也就是用户的提示符,如果我们想要永久修改提示符,就要在这个文件中修改
umask:定义umask默认权限。这个文件中定义的umask是针对“没有登录过程(也就是不需要输入用户名和密码时,比如从一个终端切换到另一个终端,或进入子Shell)”时生效的。如果是“有用户登录过程”,则是/etc/profile文件中的umask生效。
PATH变量:会给PATH变量追加值,当然也是在“没有登录过程”时才生效。
调用/etc/profile.d/*.sh文件,这也是在“没有用户登录过程”是才调用。在“有用户登录过程”时,/etc/profile.d/*.sh文件已经被/etc/profile文件调用过了。
这样这五个环境变量配置文件会被依次调用,那么如果是我们自己定义的环境变量应该放入哪个文件呢?如果你的修改是打算对所有用户生效的,那么可以放入/etc/profile环境变量配置文件;如果你的修改只是给自己使用的,那么可以放入~/.bash_profile或~/.bashrc这两个配置文件中的任一个。
可是如果我们误删除了这些环境变量,比如删除了/etc/bashrc文件,或删除了~/.bashrc文件,那么这些文件中配置就会失效(~/.bashrc文件会调用/etc/bashrc文件)。那么我们的提示符就会变成:
-bash-4.1#
2)、 注销时生效的环境变量配置文件
~/.bash_logout。
使用logout命令
在用户退出登录时,只会调用一个环境变量配置文件,就是~/.bash_logout。这个文件默认没有写入任何内容,可是如果我们希望再退出登录时执行一些操作,比如清除历史命令,备份某些数据,就可以把命令写入这个文件。
3)、 其他配置文件
还有一些环节变量配置文件,最常见的就是~/bash_history文件,也就是历史命令保存文件。这个文件已经讲过了,这里我们只是把它归入环境变量配置文件小节而已。
3 Shell登录信息
1)、 /etc/issue
我们在登录tty1-tty6这六个本地终端时,会有几行的欢迎界面。这些欢迎信息是保存在哪里的?可以修改吗?当然可以修改,这些欢迎信息是保存在/etc/issue文件中,我们查看下这个文件:
[root@localhost ~]# cat /etc/issueCentOS release 6.8 (Final)Kernel \ on an \\m
可以支持的转义符我们可以通过man agetty命令查询,在表中我们列出常见的转义符作用:
\\d
显示当前系统日期
\\s
显示操作系统名称
\\l
显示登录的终端号,这个比较常用。
\\m
显示硬件体系结构,如i386、i686等
\
显示主机名
\\o
显示域名
显示内核版本
\\t
显示当前系统时间
\\u
显示当前登录用户的序列号
2)、 /etc/issue.net
/etc/issue是在本地终端登录是显示欢迎信息的,如果是远程登录(如ssh远程登录,或telnet远程登录)需要显示欢迎信息,则需要配置/etc/issue.net这个文件了。使用这个文件时由两点需要注意:
首先,在/etc/issue文件中支持的转义符,在/etc/issue.net文件中不能使用。
其次,ssh远程登录是否显示/etc/issue.net文件中的欢迎信息,是由ssh的配置文件决定的。
如果我们需要ssh远程登录可以查看/etc/issue.net的欢迎信息,那么首先需要修改ssh的配置文件/etc/ssh/sshd_config,加入如下内容:
[root@localhost ~]# cat /etc/ssh/sshd_config…省略部分输出…# no default banner path#Banner noneBanner /etc/issue.net…省略部分输出…
这样在ssh远程登录时,也可以显示欢迎信息,只是不再可以识别“\\d”和“\\l”等信息了
3)、 /etc/motd
/etc/motd文件中也是显示欢迎信息的,这个文件和/etc/issue及/etc/issue.net文件的区别是:/etc/issue及/etc/issue.net是在用户登录之前显示欢迎信息,而/etc/motd是在用户输入用户名和密码,正确登录之后显示欢迎信息。在/etc/motd文件中的欢迎信息,不论是本地登录,还是远程登录都可以显示。
4 定义Bash快捷键
[root@localhost ~]# stty -a#查询所有的快捷键
那么这些快捷键可以更改吗?可以啊,只要执行:
[root@localhost ~]# stty 关键字 快捷键例如:[root@localhost ~]# stty intr ^p#定义ctrl+p快捷键为强制终止,“^”字符只要手工输入即可[root@localhost ~]# stty -aspeed 38400 baud; rows 21; columns 104; line = 0;intr = ^P; quit = ^\\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>;start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;#强制终止变成了ctrl+p快捷键
Shell编程
一、正则表达式
1、概述
正则表达式
用来匹配文件中符合条件的字符串
通配符
用来匹配符合条件的文件名
其实这种区别只在 Shell 当中适用
用来在文件当中搜索字符串的命令,如 grep、awk、sed 等命令可以支持正则表达式
在系统当中搜索文件的命令,如 ls、find、cp 这些命令不支持正则表达式,只能使用 shell 自己的通配符来进行匹配了
2 基础正则表达式
* 前一个字符匹配 0 次或任意多次。
. 匹配除了换行符外任意一个字符。
^ 匹配行首。例如:^hello 会匹配以 hello 开头的行。
$ 匹配行尾。例如:hello&会匹配以 hello 结尾的行。
[] 匹配中括号中指定的任意一个字符,只匹配一个字符。
例如:[aoeiu] 匹配任意一个元音字母,[0-9] 匹配任意一位数字,[a-z][0-9]匹配小写字和一位数字构成的两位字符。
[^] 匹配除中括号的字符以外的任意一个字符。
例如:[^0-9] 匹配任意一位非数字字符,[^a-z] 表示任意一位非小写字母。
\\ 转义符。用于取消讲特殊符号的含义取消。
\\{n\\} 表示其前面的字符恰好出现 n 次。
例如:[0-9]\\{4\\} 匹配 4 位数字,[1][3-8][0-9]\\{9\\} 匹配手机号码。
实例
1)、 练习文件建立
2)、 “*”前一个字符匹配 0 次,或任意多次
3)、 “.” 匹配除了换行符外任意一个字符
正则表达式“.”只能匹配一个字符,这个字符可以是任意字符
[root@localhost ~]# grep \"s..d\
[root@localhost ~]# grep \".*\" test_rule.txt
4)、 “^”匹配行首,“$”匹配行尾
“^”代表匹配行首,比如“^M”会匹配以大写“M”开头的行:
[root@localhost ~]# grep \"^M\" test_rule.txt Mr. Li Ming said:Mr. Shen Chao is the most honest man
“$”代表匹配行尾,如果“n$”会匹配以小写“n”结尾的行:
[root@localhost ~]# grep \"n$\" test_rule.txt Mr. Shen Chao is the most honest man
而“^$”则会匹配空白行:
[root@localhost ~]# grep -n \"^$\" test_rule.txt
5)、 “[]” 匹配中括号中指定的任意一个字符,只匹配一个字符
6)“[^]” 匹配除中括号的字符以外的任意一个字符
“[^]” 匹配除中括号的字符以外的任意一个字符
[root@localhost ~]# grep \"^[^a-z]\" test_rule.txt
而“^[^a-zA-Z]”则会匹配不用字母开头的行:
[root@localhost ~]# grep \"^[^a-zA-Z]\" test_rule.txt
7)、 “\\” 转义符
[root@localhost ~]# grep \"\\.$\" test_rule.txt
8)、 “\\{n\\}”表示其前面的字符恰好出现 n 次
3 扩展正则表达式
熟悉正则表达式的童鞋应该很疑惑,在正则表达式中应该还可以支持一些元字符,比如“+”“?”“|”“()”。
熟悉正则表达式的童鞋应该很疑惑,在正则表达式中应该还可以支持一些元字符,比如“+”“?”“|”“()”。其实 Linux 是支持这些元字符的,只是 grep 命令默认不支持而已。如果要想支持这些元字符,必须使用 egrep 命令或 grep -E 选项,所以我们又把这些元字符称作扩展元字符。
如果查询 grep 的帮助,对 egrep 的说明就是和 grep -E 选项一样的命令,所以我们可以把两个命令当做别名来对待。
扩展元字符
+
前一个字符匹配 1 次或任意多次。
如“go+gle”会匹配“gogle”、“google”或“gooogle”,当然如果“o”有更多个,也能匹配。
?
前一个字符匹配 0 次或 1 次。
如“colou?r”可以匹配“colour”或“color”。
匹配两个或多个分支选择。
如“was|his”会匹配既包含“was”的行,也匹配包含“his”的行。
()
匹配其整体为一个字符,即模式单元。可以理解为由多个单个字符组成的大字符。
如“(dog)+”会匹配“dog”、“dogdog”、“dogdogdog”等,因为被()包含的字符会当成一个整体。但“hello (world|earth)”会匹配“hello world”及“hello earth”。
匹配邮箱
匹配IP
二、 字符截取和替换命令
1 cut 列提取命令
[root@localhost ~]# cut [选项] 文件名选项:-f 列号: 提取第几列-d 分隔符: 按照指定分隔符分割列-c 字符范围: 不依赖分隔符来区分列,而是通过字符范围(行首为 0)来进行字段提取。“n-”表示从第 n 个字符到行尾;“n-m”从第 n 个字符到第 m个字符;“-m”表示从第 1 个字符到第 m 个字符。
cut不能使用空格进行分隔
cut 命令的默认分隔符是制表符,也就是“tab”键,不过对空格符可是支持的不怎么好啊。我们先建立一个测试文件,然后看看 cut 命令的作用吧:
[root@localhost ~]# vi student.txtID Name gender Mark1 Liming M 862 Sc M 903 Tg M 83
[root@localhost ~]# cut -f 2 student.txt#提取第二列内容
那如果想要提取多列呢?只要列号直接用“,”分开,命令如下:
cut 可以按照字符进行提取,需要注意“8-”代表的是提取所有行的第十个字符开始到行尾,而“10-20”代表提取所有行的第十个字符到第二十个字符,而“-8”代表提取所有行从行首到第八个字符:
[root@localhost ~]# cut -c 8- student.txt #提取第八个字符开始到行尾,好像很乱啊,那是因为每行的字符个数不相等啊
[root@localhost ~]# cut -d \":\
2 awk编程
1)、 概述
2)、 printf格式化输出
[root@localhost ~]# printf ‘输出类型输出格式’ 输出内容输出类型:%ns: 输出字符串。n是数字指代输出几个字符%ni: 输出整数。n是数字指代输出几个数字%m.nf: 输出浮点数。m和n是数字,指代输出的整数位数和小数位数。如%8.2f代表共输出8位数,其中2位是小数,6位是整数。输出格式:\\a: 输出警告声音\\b: 输出退格键,也就是Backspace键\\f: 清除屏幕\: 换行\: 回车,也就是Enter键\\t: 水平输出退格键,也就是Tab键\\v: 垂直输出退格键,也就是Tab键
为了演示printf命令,我们需要修改下刚刚cut命令使用的student.txt文件,文件内容如下:
[root@localhost ~]# vi student.txtID Name PHP Linux MySQL Average1 Liming 82 95 86 87.662 Sc 74 96 87 85.663 Tg 99 83 93 91.66
我们使用printf命令输出下这个文件的内容:
[root@localhost ~]# printf '%s' $(cat student.txt)IDNamegenderPHPLinuxMySQLAverage1LimingM82958687.662ScM74968785.663TgM99839391.66[root@localhost ~]#
如果不指定输出格式,则会把所有输出内容连在一起输出。其实文本的输出本身就是这样的,cat等文本输出命令之所以可以按照格式漂亮的输出,那是因为cat命令已经设定了输出格式。
[root@localhost ~]# printf '%s\\t %s\\t %s\\t %s\\t %s\\t %s\\t \' $(cat student.txt)#注意在printf命令的单引号中,只能识别格式输出符号,而手工输入的空格是无效的ID Name PHP Linux MySQL Average1 Liming 82 95 86 87.662 Sc 74 96 87 85.663 Tg 99 83 93 91.66
如果不想把成绩当成字符串输出,而是按照整型和浮点型输出,则要这样:
[root@localhost ~]# printf '%i\\t %s\\t %i\\t %i\\t %i\\t %8.2f\\t \' \\$(cat student.txt | grep -v Name)
printf 使用单引号,在awk中使用双引号
3)、 awk基本使用
awk支持
print
不加\
自动换行
printf
换行需要加\
[root@localhost ~]# awk ‘条件1{动作1} 条件2{动作2}…’ 文件名条件(Pattern):一般使用关系表达式作为条件。这些关系表达式非常多,具体参考表12-3所示,例如:x > 10 判断变量 x是否大于10x == y 判断变量 x是否等于变量yA ~ B 判断字符串A中是否包含能匹配B 表达式的子字符串A !~ B 判断字符串A中是否不包含能匹配B表达式的子字符串动作(Action):格式化输出流程控制语句
[root@localhost ~]# awk '{printf $2 \"\\t\" $6 \"\\"}' student.txt#输出第二列和第六列
比如刚刚截取df命令的结果时,cut命令已经力不从心了,我们来看看awk命令:
[root@localhost ~]# df -h | awk '{print $1 \"\\t\" $3}'
4)、 awk的条件
条件的类型
awk保留字
BEGIN
在awk程序一开始时,尚未读取任何数据之前执行。BEGIN后的动作只在程序开始时执行一次
END
在awk程序处理完所有数据,即将结束时执行。END后的动作只在程序结束时执行一次
关系运算符
>
大于
<
小于
>=
大于等于
<=
小于等于
==
等于。用于判断两个值是否相等,如果是给变量赋值,请使用“=”号
!=
不等于
A~B
判断字符串A中是否包含能匹配B 表达式的子字符串
A!~B
判断字符串A中是否不包含能匹配B表达式的子字符串
/正则/
如果在“//”中可以写入字符,也可以支持正则表达式
BEGIN是awk的保留字,是一种特殊的条件类型。BEGIN的执行时机是“在awk程序一开始时,尚未读取任何数据之前执行”。一旦BEGIN后的动作执行一次,当awk开始从文件中读入数据,BEGIN的条件就不再成立,所以BEGIN定义的动作只能被执行一次。
[root@localhost ~]# awk 'BEGIN{printf \"This is a transcript \\" }{printf $2 \"\\t\" $6 \"\\
END也是awk保留字,不过刚好和BEGIN相反。END是在awk程序处理完所有数据,即将结束时执行。END后的动作只在程序结束时执行一次。例如:
[root@localhost ~]# awk 'END{printf \"The End \\" }{printf $2 \"\\t\" $6 \"\\"}' student.txt#在输出结尾输入“The End”,这并不是文档本身的内容,而且只会执行一次
例子1:[root@localhost ~]# cat student.txt | grep -v Name | \\awk '$6 >= 87 {printf $2 \"\\" }'#使用cat输出文件内容,用grep取反包含“Name”的行#判断第六字段(平均成绩)大于等于87分的行,如果判断式成立,则打第六列(学员名)
加入了条件之后,只有条件成立动作才会执行,如果条件不满足,则动作则不运行。通过这个实验,大家可以发现,虽然awk是列提取命令,但是也要按行来读入的。这个命令的执行过程是这样的:
1) 如果有BEGIN条件,则先执行BEGIN定义的动作
2) 如果没有BEGIN条件,则读入第一行,把第一行的数据依次赋予$0、$1、$2等变量。其中$0代表此行的整体数据,$1代表第一字段,$2代表第二字段。
3) 依据条件类型判断动作是否执行。如果条件符合,则执行动作,否则读入下一行数据。如果没有条件,则每行都执行动作。
4) 读入下一行数据,重复执行以上步骤。
再举个例子,如果我想看看Sc用户的平均成绩呢
例子2:[root@localhost ~]# awk '$2 ~ /Sc/ {printf $6 \"\\"}' student.txt#如果第二字段中输入包含有“Sc”字符,则打印第六字段数据85.66
这里要注意在awk中,使用“//”包含的字符串,awk命令才会查找。也就是说字符串必须用“//”包含,awk命令才能正确识别。
如果要想让awk识别字符串,必须使用“//”包含,例如:
例子1:[root@localhost ~]# awk '/Liming/ {print}' student.txt#打印Liming的成绩
当使用df命令查看分区使用情况是,如果我只想查看真正的系统分区的使用状况,而不想查看光盘和临时分区的使用状况,则可以:
例子2:[root@localhost ~]# df -h | awk '/sda[0-9]/ {printf $1 \"\\t\" $5 \"\\"} '#查询包含有sda数字的行,并打印第一字段和第五字段
5)、 awk内置变量
$0
代表目前awk所读入的整行数据。我们已知awk是一行一行读入数据的,$0就代表当前读入行的整行数据。
代表目前读入行的第n个字段。
NF
当前行拥有的字段(列)总数。
NR
当前awk所处理的行,是总数据的第几行。
FS
用户定义分隔符。awk的默认分隔符是任何空格或Tab,如果想要使用其他分隔符(如“:”),就需要FS变量定义。
ARGC
命令行参数个数。
ARGV
命令行参数数组。
FNR
当前文件中的当前记录数(对输入文件起始为1)。
OFMT
数值的输出格式(默认为%.6g)。
OFS
输出字段的分隔符(默认为空格)。
ORS
输出记录分隔符(默认为换行符)。
RS
输入记录分隔符(默认为换行符)。
[root@localhost ~]# cat /etc/passwd | grep \"/bin/bash\" | \\awk '{FS=\":\"} {printf $1 \"\\t\" $3 \"\\"}'#查询可以登录的用户的用户名和UID
这里“:”分隔符生效了,可是第一行却没有起作用,原来我们忘记了“BEGIN”条件,那么再来试试:
[root@localhost ~]# cat /etc/passwd | grep \"/bin/bash\" | \\awk 'BEGIN {FS=\":\"} {printf $1 \"\\t\" $3 \"\\"}'
加begin可以提前执行命令
原因是,awk提前把第一行数据读到了,并且没有进行用“:”分隔,所以打印时直接打印出了连续的数据
[root@localhost ~]# cat /etc/passwd | grep \"/bin/bash\" | \\awk 'BEGIN {FS=\":\"} {printf $1 \"\\t\" $3 \"\\t 行号:\" NR \"\\t 字段数:\" NF \"\\"}'#解释下awk命令#开始执行{分隔符是“:”} {输出第一字段和第三字段 输出行号(NR值) 字段数(NF值)}root 0 行号:1 字段数:7user1 501 行号:2 字段数:7
printf中变量不用双引号包括,字符串用双引号包括
如果我只想看看sshd这个伪用户的相关信息,则可以这样使用:
[root@localhost ~]# cat /etc/passwd | \\awk 'BEGIN {FS=\":\"} $1==\"sshd\" {printf $1 \"\\t\" $3 \"\\t 行号:\"NR \"\\t 字 段数:\"F \"\\"}'#可以看到sshd伪用户的UID是74,是/etc/passwd文件的第28行,此行有7个字段
6)、 awk流程控制
我们再来利用下 student.txt 文件做个练习,后面的使用比较复杂,我们再看看这个文件的内容:
[root@localhost ~]# cat student.txt ID Name PHP Linux MySQL Average1 Liming 82 95 86 87.66[root@localhost ~]# cat student.txt ID Name PHP Linux MySQL Average1 Liming 82 95 86 87.66
统计 PHP 成绩的总分,那么就应该这样:
[root@localhost ~]# awk 'NR==2{php1=$3} NR==3{php2=$3} NR==4{php3=$3;totle=php1+php2+php3;print \"totle php is \" totle}' student.txt #统计 PHP 成绩的总分
“NR==2{php1=$3}”(条件是 NR==2,动作是 php1=$3)这句话是指如果输入数据是第二行(第一行是标题行),就把第二行的第三字段的值赋予变量“php1”。“NR==3{php2=$3}”这句话是指如果输入数据是第三行,就把第三行的第三字段的值赋予变量“php2。“NR==4{php3=$3;totle=php1+php2+php3;print \"totle php is \" totle}”(“NR==4”是条件,后面{}中的都是动作)这句话是指如果输入数据是第四行,就把第四行的第三字段的值赋予变量“php3”;然后定义变量 totle 的值是“php1+php2+php3”;然后输出“totle php is”关键字,后面加变量 totle的值。
在 awk 编程中,因为命令语句非常长,在输入格式时需要注意以下内容:
多个条件{动作}可以用空格分割,也可以用回车分割。 在一个动作中,如果需要执行多个命令,需要用“;”分割,或用回车分割。 在 awk 中,变量的赋值与调用都不需要加入“$”符。 条件中判断两个值是否相同,请使用“==”,以便和变量赋值进行区分。
如果 Linux 成绩大于 90,就是一个好男人(学 PHP 的表示压力很大!)
[root@localhost ~]# awk '{if (NR>=2) {if ($4>90) printf $2 \" is a good man!\\"}}' student.txt #程序中有两个 if 判断,第一个判断行号大于 2,第二个判断 Linux 成绩大于 90 分Liming is a good man!Sc is a good man!
完全可以直接利用 awk 自带的条件来取代,刚刚的脚本可以改写成这样:
[root@localhost ~]# awk ' NR>=2 {test=$4} test>90 {printf $2 \" is a good man!\\"}' student.txt #先判断行号如果大于 2,就把第四字段赋予变量 test#在判断如果 test 的值大于 90 分,就打印好男人Liming is a good man!Sc is a good man!
7)、 awk函数
awk 编程也允许在编程时使用函数,awk 函数的定义方法如下:
function 函数名(参数列表){函数体}
8)、 awk中调用脚本
对于小的单行程序来说,将脚本作为命令行自变量传递给 awk 是非常简单的,而对于多行程序就比较难处理。当程序是多行的时候,使用外部脚本是很适合的。首先在外部文件中写好脚本,然后可以使用 awk 的-f 选项,使其读入脚本并且执行。
先编写一个 awk 脚本:
[root@localhost ~]# vi pass.awkBEGIN {FS=\":\"}{ print $1 \"\\t\" $3}
然后可以使用“-f”选项来调用这个脚本:
[root@localhost ~]# awk -f pass.awk /etc/passwdroot 0bin 1daemon 2…省略部分输出…
3 sed命令
sed 主要是用来将数据进行选取、替换、删除、新增的命令,我们看看命令的语法:
语法
[root@localhost ~]# sed [选项] ‘[动作]’ 文件名选项:-n: 一般 sed 命令会把所有数据都输出到屏幕,如果加入此选择,则只会把经过 sed 命令处理的行输出到屏幕。-e: 允许对输入数据应用多条 sed 命令编辑。-f 脚本文件名: 从 sed 脚本中读入 sed 操作。和 awk 命令的-f 非常类似。-r: 在 sed 中支持扩展正则表达式。-i: 用 sed 的修改结果直接修改读取数据的文件,而不是由屏幕输出动作:a \\: 追加,在当前行后添加一行或多行。添加多行时,除最后 一行外,每行末尾需要用“\\”代表数据未完结。c \\: 行替换,用 c 后面的字符串替换原数据行,替换多行时,除最后一行外,每行末尾需用“\\”代表数据未完结。i \\: 插入,在当期行前插入一行或多行。插入多行时,除最后 一行外,每行末尾需要用“\\”代表数据未完结。d: 删除,删除指定的行。p: 打印,输出指定的行。s: 字串替换,用一个字符串替换另外一个字符串。格式为“行范围 s/旧字串/新字串/g”(和 vim 中的替换格式类似)。
对 sed 命令大家要注意,sed 所做的修改并不会直接改变文件的内容(如果是用管道符接收的命令的输出,这种情况连文件都没有),而是把修改结果只显示到屏幕上,除非使用“-i”选项才会直接修改文件。
行数据操作
可以利用“p”动作
[root@localhost ~]# sed '2p' student.txtID Name PHP Linux MySQL Average1 Liming 82 95 86 87.661 Liming 82 95 86 87.662 Sc 74 96 87 85.663 Tg 99 83 93 91.66
好像看着不怎么顺眼啊!“p”命令确实输出了第二行数据,但是sed命令还会把所有数据都输出一次,这时就会看到这个比较奇怪的结果。那如果我想指定输出某行数据,就需要“-n”选项的帮助了:
[root@localhost ~]# sed -n '2p' student.txt1 Liming 82 95 86 87.66
再来看看如何删除文件的数据:
再来看看如何追加和插入行数据:
[root@localhost ~]# sed '2a hello' student.txt#在第二行后加入hello
“a”会在指定行后面追加入数据,如果想要在指定行前面插入数据,则需要使用“i”动作:
[root@localhost ~]# sed '2i hello \\> world' student.txt#在第二行前插入两行数据
如果是想追加或插入多行数据,除最后一行外,每行的末尾都要加入“\\”代表数据未完结。再来看看“-n”选项的作用:
[root@localhost ~]# sed -n '2i hello \\#只查看sed命令操作的数据> world' student.txt
“-n”只查看sed命令操作的数据,而不是查看所有数据。
可以这样:
[root@localhost ~]# cat student.txt | sed '2c No such person'
[root@localhost ~]# sed -i '2c No such person' student.txt
ed命令默认情况是不会修改文件内容的,如果我确定需要让sed命令直接处理文件的内容,可以使用“-i”选项。
字符串替换
“c”动作是进行整行替换的,如果仅仅想替换行中的部分数据,就要使用“s”动作了。s动作的格式是:
[root@localhost ~]# sed ‘s/旧字串/新字串/g’ 文件名
替换的格式和vim非常类似
[root@localhost ~]# sed '3s/74/99/g' student.txt#在第三行中,把74换成99
把Tg老师的成绩注释掉
[root@localhost ~]# sed '4s/^/#/g' student.txt#这里使用正则表达式,“^”代表行首
删除字符串
[root@localhost ~]# sed -e 's/Liming//g ; s/Tg//g' student.txt#同时把“Liming”和“Tg”替换为空
-e”选项可以同时执行多个sed动作,当然如果只是执行一个动作也可以使用“-e”选项,但是这时没有什么意义。还要注意,多个动作之间要用“;”号或回车分割,例如上一个命令也可以这样写:
[root@localhost ~]# sed -e 's/Liming//g> s/Tg//g' student.txt
三、字符处理命令
1 排序命令sort
sort命令默认是用每行开头第一个字符来进行排序的,比如:
[root@localhost ~]# sort /etc/passwd#排序用户信息文件
概要
[root@localhost ~]# sort -r /etc/passwd#反向排序
如果想要指定排序的字段,需要使用“-t”选项指定分隔符,并使用“-k”选项指定字段号。加入我想要按照UID字段排序/etc/passwd文件:
[root@localhost ~]# sort -t \":\
看起来好像很美,可是如果仔细看看,怎么daemon用户的UID是2,反而排在了下面?这是因为sort默认是按照字符排序,前面用户的UID的第一个字符都是1,所以这么排序。要想按照数字排序,请使用“-n”选项:
[root@localhost ~]# sort -n -t \":\
当然“-k”选项可以直接使用“-k 3”,代表从第三字段到行尾都排序(第一个字符先排序,如果一致,第二个字符再排序,知道行尾)。
2 uniq
uniq命令是用来取消重复行的命令,其实和“sort -u”选项是一样的。命令格式如下:
[root@localhost ~]# uniq [选项] 文件名选项:-i: 忽略大小写
3 统计命令wc
[root@localhost ~]# wc [选项] 文件名选项:-l: 只统计行数-w: 只统计单词数-m: 只统计字符数
四、 条件判断
1 按照文件类型进行判断
来看看test可以进行哪些文件类型的判断:
-b 文件
判断该文件是否存在,并且是否为块设备文件(是块设备文件为真)
-c文件
判断该文件是否存在,并且是否为字符设备文件(是字符设备文件为真)
-d 文件
判断该文件是否存在,并且是否为目录文件(是目录为真)
-e 文件
判断该文件是否存在(存在为真)
-f 文件
判断该文件是否存在,并且是否为普通文件(是普通文件为真)
-L 文件
判断该文件是否存在,并且是否为符号链接文件(是符号链接文件为真)
-p 文件
判断该文件是否存在,并且是否为管道文件(是管道文件为真)
-s 文件
判断该文件是否存在,并且是否为非空(非空为真)
-S 文件
判断该文件是否存在,并且是否为套接字文件(是套接字文件为真)
[root@localhost ~]# [ -e /root/sh/ ][root@localhost ~]# echo $?0#判断结果为0,/root/sh/目录是存在的[root@localhost ~]# [ -e /root/test ][root@localhost ~]# echo $?1#在/root/下并没有test文件或目录,所以“$?”的返回值为非零
还记得多命令顺序执行的“&&”和“||”吗?我们可以再判断一下/root/sh/是否是目录:
[root@localhost ~]# [ -d /root/sh ] && echo \"yes\" || echo \"no\"#第一个判断命令如果正确执行,则打印“yes”,否则打印“no”
test命令 等同于[ ]
2 按照文件权限进行判断
test是非常完善的判断命令,还可以判断文件的权限,我们通过表12-6来看看:
-r 文件
判断该文件是否存在,并且是否该文件拥有读权限(有读权限为真)
-w文件
判断该文件是否存在,并且是否该文件拥有写权限(有写权限为真)
-x 文件
判断该文件是否存在,并且是否该文件拥有执行权限(有执行权限为真)
-u 文件
判断该文件是否存在,并且是否该文件拥有SUID权限(有SUID权限为真)
-g 文件
判断该文件是否存在,并且是否该文件拥有SGID权限(有SGID权限为真)
-k 文件
判断该文件是否存在,并且是否该文件拥有SBit权限(有SBit权限为真)
比如:
[root@localhost ~]# ll student.txt-rw-r--r--. 1 root root 97 6月 7 07:34 student.txt[root@localhost ~]# [ -w student.txt ] && echo \"yes\" || echo \"no\"yes#判断文件是拥有写权限的
只能判断整个文件是否有写权限,不能区分拥有者,如果要区分需要截取出来再进行判断
3 两个文件之间进行比较
文件1 -nt 文件2
判断文件1的修改时间是否比文件2的新(如果新则为真)
文件1 -ot 文件2
判断文件1的修改时间是否比文件2的旧(如果旧则为真)
文件1 -ef 文件2
判断文件1是否和文件2的Inode号一致,可以理解为两个文件是否为同一个文件。这个判断用于判断硬链接是很好的方法
如何判断两个文件是否是硬链接呢?这时test就派上用场了:
[root@localhost ~]# ln /root/student.txt /tmp/stu.txt#创建个硬链接吧[root@localhost ~]# [ /root/student.txt -ef /tmp/stu.txt ] && echo \"yes\" || echo \"no\"yes#用test测试下,果然很有用
4 两个整数之间比较
“==”在shell中用来比较字符串是否相等
整数1 -eq 整数2
判断整数1是否和整数2相等(相等为真)
整数1 -ne 整数2
判断整数1是否和整数2不相等(不相等位置)
整数1 -gt 整数2
判断整数1是否大于整数2(大于为真)
整数1 -lt 整数2
判断整数1是否小于整数2(小于位置)
整数1 -ge 整数2
判断整数1是否大于等于整数2(大于等于为真)
整数1 -le 整数2
判断整数1是否小于等于整数2(小于等于为真)
举个例子:
[root@localhost ~]# [ 23 -ge 22 ] && echo \"yes\" || echo \"no\"yes#判断23是否大于等于22,当然是了[root@localhost ~]# [ 23 -le 22 ] && echo \"yes\" || echo \"no\"no#判断23是否小于等于22,当然不是了
5 字符串的判断
-z 字符串
判断字符串是否为空(为空返回真)
-n 字符串
判断字符串是否为非空(非空返回真)
字串1 ==字串2
判断字符串1是否和字符串2相等(相等返回真)
字串1 != 字串2
判断字符串1是否和字符串2不相等(不相等返回真)
[root@localhost ~]# name=sc#给name变量赋值[root@localhost ~]# [ -z \"$name\" ] && echo \"yes\" || echo \"no\"no#判断name变量是否为空,因为不为空,所以返回no
再来看看如何判断两个字符串相等:
[root@localhost ~]# aa=11[root@localhost ~]# bb=22#给变量aa和变量bb赋值[root@localhost ~]# [ \"$aa\" == \"bb\" ] && echo \"yes\" || echo \"no\"no#判断两个变量的值是否相等,明显不相等,所以返回no
6 多重条件判断
测试选项
判断1 -a 判断2
逻辑与,判断1和判断2都成立,最终的结果才为真
判断1 -o 判断2
逻辑或,判断1和判断2有一个成立,最终的结果就为真
!判断
逻辑非,使原始的判断式取反
[root@localhost ~]# aa=11#给变量aa赋值[root@localhost ~]# [ -n \"$aa\" -a \"$aa\" -gt 23 ] && echo \"yes\" || echo \"no\"no#判断变量aa是否有值,同时判断变量aa的是否大于23#因为变量aa的值不大于23,所以虽然第一个判断值为真,返回的结果也是假
[root@localhost ~]# aa=24[root@localhost ~]# [ -n \"$aa\" -a \"$aa\" -gt 23 ] && echo \"yes\" || echo \"no\"yes
逻辑非
[root@localhost ~]# [ ! -n \"$aa\" ] && echo \"yes\" || echo \"no\"no#本来“-n”选项是变量aa不为空,返回值就是真。#加入!之后,判断值就会取反,所以当变量aa有值时,返回值是假
注意:“!”和“-n”之间必须加入空格,否则会报错的。
五、 流程控制
1 if条件判断
1)、 单分支if条件语句
单分支条件语句最为简单,就是只有一个判断条件,如果符合条件则执行某个程序,否则什么事情都不做。语法如下:
if [ 条件判断式 ];then程序fi
if [ 条件判断式 ]then程序fi
[root@localhost ~]# vi sh/if1.sh#!/bin/bash#统计根分区使用率# Author: shenchao (E-mail: shenchao@atguigu.com)rate=$(df -h | grep \"/dev/sda3\" | awk '{print $5}' | cut -d \"%\" -f1)#把根分区使用率作为变量值赋予变量rateif [ $rate -ge 80 ]#判断rate的值如果大于等于80,则执行then程序thenecho \"Warning! /dev/sda3 is full!!\"#打印警告信息。在实际工作中,也可以向管理员发送邮件。fi
单分支条件语句需要注意几个点:
if语句使用fi结尾,和一般语言使用大括号结尾不同
[ 条件判断式 ]就是使用test命令判断,所以中括号和条件判断式之间必须有空格
then后面跟符合条件之后执行的程序,可以放在[]之后,用“;”分割。也可以换行写入,就不需要“;”了
2)、 双分支if条件语句
if [ 条件判断式 ]then条件成立时,执行的程序else条件不成立时,执行的另一个程序fi
写一个数据备份的例子,来看看双分支if条件语句。
例子1:备份mysql数据库[root@localhost ~]# vi sh/bakmysql.sh#!/bin/bash#备份mysql数据库。# Author: shenchao (E-mail: shenchao@atguigu.com)ntpdate asia.pool.ntp.org &>/dev/null#同步系统时间date=$(date +%y%m%d)#把当前系统时间按照“年月日”格式赋予变量datesize=$(du -sh /var/lib/mysql)#统计mysql数据库的大小,并把大小赋予size变量if [ -d /tmp/dbbak ]#判断备份目录是否存在,是否为目录then#如果判断为真,执行以下脚本echo \"Date : $date!\" > /tmp/dbbak/dbinfo.txt#把当前日期写入临时文件echo \"Data size : $size\" >> /tmp/dbbak/dbinfo.txt#把数据库大小写入临时文件cd /tmp/dbbak#进入备份目录tar -zcf mysql-lib-$date.tar.gz /var/lib/mysql dbinfo.txt &>/dev/null#打包压缩数据库与临时文件,把所有输出丢入垃圾箱(不想看到任何输出)rm -rf /tmp/dbbak/dbinfo.txt#删除临时文件elsemkdir /tmp/dbbak#如果判断为假,则建立备份目录echo \"Date : $date!\" > /tmp/dbbak/dbinfo.txtecho \"Data size : $size\" >> /tmp/dbbak/dbinfo.txt#把日期和数据库大小保存如临时文件cd /tmp/dbbaktar -zcf mysql-lib-$date.tar.gz dbinfo.txt /var/lib/mysql &>/dev/null#压缩备份数据库与临时文件rm -rf /tmp/dbbak/dbinfo.txt#删除临时文件fi
再举个例子,在工作当中,服务器上的服务经常会宕机。如果我们对服务器监控不好,就会造成服务器中服务宕机了,而管理员却不知道的情况,这时我们可以写一个脚本来监听本机的服务,如果服务停止或宕机了,可以自动重启这些服务。我们拿apache服务来举例:
例子2:判断apache是否启动,如果没有启动则自动启动[root@localhost ~]# vi sh/autostart.sh#!/bin/bash#判断apache是否启动,如果没有启动则自动启动# Author: shenchao (E-mail: shenchao@atguigu.com)port=$(nmap -sT 192.168.4.210 | grep tcp | grep http | awk '{print $2}')#使用nmap命令扫描服务器,并截取apache服务的状态,赋予变量portif [ \"$port\" == \"open\" ]#如果变量port的值是“open”thenecho “$(date) httpd is ok!” >> /tmp/autostart-acc.log#则证明apache正常启动,在正常日志中写入一句话即可else/etc/rc.d/init.d/httpd start &>/dev/null#否则证明apache没有启动,自动启动apacheecho \"$(date) restart httpd !!\" >> /tmp/autostart-err.log#并在错误日志中记录自动启动apche的时间fi
以我们使用nmap端口扫描命令,nmap命令格式如下:
[root@localhost ~]# nmap -sT 域名或IP选项:-s 扫描-T 扫描所有开启的TCP端口
[root@localhost ~]# nmap -sT 192.168.4.210#可以看到这台服务器开启了如下的服务Starting Nmap 5.51 ( http://nmap.org ) at 2018-11-25 15:11 CSTNmap scan report for 192.168.4.210Host is up (0.0010s latency).Not shown: 994 closed portsPORT STATE SERVICE22/tcp open ssh80/tcp open http apache的状态是open111/tcp open rpcbind139/tcp open netbios-ssn445/tcp open microsoft-ds3306/tcp open mysqlNmap done: 1 IP address (1 host up) scanned in 0.49 seconds
知道了nmap命令的用法,我们在脚本中使用的命令就是为了截取http的状态,只要状态是“open”就证明apache启动正常,否则证明apache启动错误。来看看脚本中命令的结果:
[root@localhost ~]# nmap -sT 192.168.4.210 | grep tcp | grep http | awk '{print $2}'#扫描指定计算机,提取包含tcp的行,在提取包含httpd的行,截取第二列open#把截取的值赋予变量port
3)、 多分支if条件语句
if [ 条件判断式1 ]then当条件判断式1成立时,执行程序1elif [ 条件判断式2 ]then当条件判断式2成立时,执行程序2…省略更多条件…else当所有条件都不成立时,最后执行此程序fi
那我们再写一个例子,用if多分支条件语句来判断一下用户输入的是一个文件,还是一个目录:
例子:判断用户输入的是什么文件[root@localhost ~]# vi sh/if-elif.sh#!/bin/bash#判断用户输入的是什么文件# Author: shenchao (E-mail: shenchao@atguigu.com)read -p \"Please input a filename: \" file#接收键盘的输入,并赋予变量fileif [ -z \"$file\" ]#判断file变量是否为空thenecho \
2 多分支case条件语句
case语句和if…elif…else语句一样都是多分支条件语句,不过和if多分支条件语句不同的是,case语句只能判断一种条件关系,而if语句可以判断多种条件关系。case语句语法如下:
case $变量名 in\"值1\")如果变量的值等于值1,则执行程序1;;\"值2\")如果变量的值等于值2,则执行程序2::…省略其他分支…*)如果变量的值都不是以上的值,则执行此程序;;esac
这个语句需要注意以下内容:
case语句,会取出变量中的值,然后与语句体中的值逐一比较。如果数值符合,则执行对应的程序,如果数值不符,则依次比较下一个值。如果所有的值都不符合,则执行“*)”(“*”代表所有其他值)中的程序。
case语句以“case”开头,以“esac”结尾。
每一个分支程序之后要通过“;;”双分号结尾,代表该程序段结束(千万不要忘记,超哥每次写case语句,都会忘记双分号,有点“囧”)。
写一个判断是“yes/no”的例子:
[root@localhost ~]# vi sh/case.sh#!/bin/bash#判断用户输入# Author: shenchao (E-mail: shenchao@atguigu.com)read -p \"Please choose yes/no: \" -t 30 cho#在屏幕上输出“请选择yes/no”,然后把用户选择赋予变量chocase $cho in#判断变量cho的值\"yes\")#如果是yesecho \"Your choose is yes!\"#执行程序1;;\"no\")#如果是noecho \"Your choose is no!\"#执行程序2;;*)#如果既不是yes,也不是noecho \"Your choose is error!\"#则执行此程序;;esac
3 for循环
for循环是固定循环,也就是在循环时已经知道需要进行几次的循环,有时也把for循环称为计数循环。for的语法有如下两种:
语法一:
for 变量 in 值1 值2 值3…do程序done
这种语法中for循环的次数,取决于in后面值的个数(空格分隔),有几个值就循环几次,并且每次循环都把值赋予变量。也就是说,假设in后面有三个值,for会循环三次,第一次循环会把值1赋予变量,第二次循环会把值2赋予变量,以此类推。
语法一举例:
例子1:打印时间[root@localhost ~]# vi sh/for.sh#!/bin/bash#打印时间# Author: shenchao (E-mail: shenchao@atguigu.com)for time in morning noon afternoon eveningdoecho \"This time is $time!\"done
批量解压缩脚本就应该这样写:
例子2:批量解压缩[root@localhost ~]# vi sh/auto-tar.sh#!/bin/bash#批量解压缩脚本# Author: shenchao (E-mail: shenchao@atguigu.com)cd /lamp#进入压缩包目录ls *.tar.gz > ls.log#把所有.tar.gz结尾的文件的文件覆盖到ls.log临时文件中for i in $(cat ls.log)#读取ls.log文件的内容,文件中有多少个值,就会循环多少次,每次循环把文件名赋予变量idotar -zxf $i &>/dev/null#加压缩,并把所有输出都丢弃donerm -rf /lamp/ls.log#删除临时文件ls.log
语法二:
for (( 初始值;循环控制条件;变量变化 ))do程序done
语法二中需要注意:
初始值:在循环开始时,需要给某个变量赋予初始值,如i=1;
循环控制条件:用于指定变量循环的次数,如i<=100,则只要i的值小于等于100,循环就会继续;
变量变化:每次循环之后,变量该如何变化,如i=i+1。代表每次循环之后,变量i的值都加1。
语法二举例
那语法二就和其他语言中的for循环更加类似了,也就是事先决定循环次数的固定循环了。先举个简单的例子:
例子1
例子1:从1加到100#!/bin/bash#从1加到100# Author: shenchao (E-mail: shenchao@atguigu.com)s=0for (( i=1;i<=100;i=i+1 ))#定义循环100次dos=$(( $s+$i ))每次循环给变量s赋值doneecho \"The sum of 1+2+...+100 is : $s\"#输出1加到100的和
例子2
例子2:批量添加指定数量的用户[root@localhost ~]# vi useradd.sh#!/bin/bash#批量添加指定数量的用户# Author: shenchao (E-mail: shenchao@atguigu.com)read -p \"Please input user name: \" -t 30 name#让用户输入用户名,把输入保存入变量nameread -p \"Please input the number of users: \" -t 30 num#让用户输入添加用户的数量,把输入保存入变量numread -p \"Please input the password of users: \" -t 30 pass#让用户输入初始密码,把输入保存如变量passif [ ! -z \"$name\" -a ! -z \"$num\" -a ! -z \"$pass\" ]#判断三个变量不为空theny=$(echo $num | sed 's/[0-9]//g')#定义变量的值为后续命令的结果#后续命令作用是,把变量num的值替换为空。如果能替换为空,证明num的值为数字#如果不能替换为空,证明num的值为非数字。我们使用这种方法判断变量num的值为数字if [ -z \"$y\" ]#如果变量y的值为空,证明num变量是数字thenfor (( i=1;i<=$num;i=i+1 ))#循环num变量指定的次数do/usr/sbin/useradd $name$i &>/dev/null#添加用户,用户名为变量name的值加变量i的数字echo $pass | /usr/bin/passwd --stdin $name$i &>/dev/null#给用户设定初始密码为变量pass的值donefifi
例子3
批量删除用户
[root@localhost ~]# vi sh/userdel.sh#!/bin/bash#批量删除用户# Author: shenchao (E-mail: shenchao@atguigu.com)user=$(cat /etc/passwd | grep \"/bin/bash\"|grep -v \"root\"|cut -d \":\" -f 1)#读取用户信息文件,提取可以登录用户,取消root用户,截取第一列用户名for i in $user#循环,有多少个普通用户,循环多少次douserdel -r $i#每次循环,删除指定普通用户done
4 while循环
while [ 条件判断式 ]do程序done
对while循环来讲,只要条件判断式成立,循环就会一直继续,直到条件判断式不成立,循环才会停止。
例子:1加到100#!/bin/bash#从1加到100# Author: shenchao (E-mail: shenchao@atguigu.com)i=1s=0#给变量i和变量s赋值while [ $i -le 100 ]#如果变量i的值小于等于100,则执行循环dos=$(( $s+$i ))i=$(( $i+1 ))doneecho \"The sum is: $s\"
5 until循环
再来看看until循环,和while循环相反,until循环时只要条件判断式不成立则进行循环,并执行循环程序。一旦循环条件成立,则终止循环。语法如下:
until [ 条件判断式 ]do程序done
还是写从1加到100这个例子,注意和while循环的区别:
例子:从1加到100[root@localhost ~]# vi sh/until.sh#!/bin/bash#从1加到100# Author: shenchao (E-mail: shenchao@atguigu.com)i=1s=0#给变量i和变量s赋值until [ $i -gt 100 ]#循环直到变量i的值大于100,就停止循环dos=$(( $s+$i ))i=$(( $i+1 ))doneecho \"The sum is: $s\"
6 函数
function 函数名 () {程序}
1加到100了,我们让用户自己来决定加到多少吧:
例子:
[root@localhost ~]# vi sh/function.sh#!/bin/bash#接收用户输入的数字,然后从1加到这个数字# Author: shenchao (E-mail: shenchao@atguigu.com)function sum () {#定义函数sums=0for (( i=0;i<=$1;i=i+1 ))#循环直到i大于$1为止。$1是函数sum的第一个参数#在函数中也可以使用位置参数变量,不过这里的$1指的是函数的第一个参数dos=$(( $i+$s ))doneecho \"The sum of 1+2+3...+$1 is : $s\"#输出1加到$1的和}read -p \"Please input a number: \" -t 30 num#接收用户输入的数字,并把值赋予变量numy=$(echo $num | sed 's/[0-9]//g')#把变量num的值替换为空,并赋予变量yif [ -z \"$y\" ]#判断变量y是否为空,以确定变量num中是否为数字thensum $num#调用sum函数,并把变量num的值作为第一个参数传递给sum函数elseecho \"Error!! Please input a number!\"#如果变量num的值不是数字,则输出报错信息fi
7 特殊流程控制语句
1、 exit语句
系统是有exit命令的,用于退出当前用户的登录状态。可是在Shell脚本中,exit语句是用来退出当前脚本的。也就是说,在Shell脚本中,只要碰到了exit语句,后续的程序就不再执行,而直接退出脚本。
exit的语法如下:
exit [返回值]
如果exit命令之后定义了返回值,那么这个脚本执行之后的返回值就是我们自己定义的返回值。可以通过查询$?这个变量,来查看返回值。
如果exit之后没有定义返回值,脚本执行之后的返回值是执行exit语句之前,最后执行的一条命令的返回值。
[root@localhost ~]# vi sh/exit.sh#!/bin/bash#演示exit的作用# Author: shenchao (E-mail: shenchao@atguigu.com)read -p \"Please input a number: \" -t 30 num#接收用户的输入,并把输入赋予变量numy=$(echo $num | sed 's/[0-9]//g')#如果变量num的值是数字,则把num的值替换为空,否则不替换#把替换之后的值赋予变量y[ -n \"$y\" ] && echo \"Error! Please input a number!\" && exit 18#判断变量y的值如果不为空,输出报错信息,退出脚本,退出返回值为18echo \"The number is: $num\"#如果没有退出加班,则打印变量num中的数字
这个脚本中,大家需要思考,如果我输入的不是数字,那么“echo \"The number is: $num\"”这个脚本是否会执行?当然不会,因为如果输入的不是数字,“[ -n \"$y\" ] && echo \"Error! Please input a number!\" && exit 18”这句脚本会执行,exit一旦执行脚本就会终止。执行下这个脚本:
[root@localhost ~]# chmod 755 sh/exit.sh#给脚本服务执行权限[root@localhost ~]# sh/exit.sh 执行脚本Please input a number: test 输入值不是数字,而是testError! Please input a number! 输出报错信息,而不会输出test[root@localhost ~]# echo $? 查看下返回值18 返回值居然真是18啊[root@localhost ~]# sh/exit.shPlease input a number: 10 输入数字10The number is: 10 输出数字10
2、 break语句
再来看看特殊流程控制语句break的作用,当程序执行到break语句时,会结束整个当前循环。而continue语句也是结束循环的语句,不过continue语句单次当前循环,而下次循环会继续。
[root@localhost ~]# vi sh/break.sh#!/bin/bash#演示break跳出循环# Author: shenchao (E-mail: shenchao@atguigu.com)for (( i=1;i<=10;i=i+1 ))#循环十次doif [ \"$i\" -eq 4 ]#如果变量i的值等于4thenbreak#退出整个循环fiecho $i#输出变量i的值done
执行下这个脚本,因为一旦变量i的值等于4,整个循环都会跳出,所以应该只能循环三次:
[root@localhost ~]# chmod 755 sh/break.sh[root@localhost ~]# sh/break.sh123
3、 continue语句
再来看看continue语句,continue也是结束流程控制的语句。如果在循环中,continue语句只会结束单次当前循环,也画个示意图来说明下continue语句,如图12-2所示:
还是用刚刚的脚本,不过退出语句换成continue语句
[root@localhost ~]# vi sh/continue.sh#!/bin/bash#演示continue语句# Author: shenchao (E-mail: shenchao@atguigu.com)for (( i=1;i<=10;i=i+1 ))doif [ \"$i\" -eq 4 ]thencontinue#退出语句换成continuefiecho $idone
运行下这个脚本:
[root@localhost ~]# chmod 755 sh/continue.sh#赋予执行权限[root@localhost ~]# sh/continue.sh1235 少了4这个输出678910
continue只会退出单次循环,所以并不影响后续的循环,所以只会少4的输出。这个例子和break的例子做个比较,应该可以更清楚的说明break和continue的区别。
0 条评论
回复 删除
下一页