Shell脚本编程
Shell历史
- shell:命令解释器,作用是解释执行用户的命令
- 执行命令方式:
- 交互式:用户输入一条命令,Shell就解释执行一条
- 批处理:用户事先编写好一个脚本,里面包含多条命令,让Shell一次性执行完这些命令
- 版本(仅介绍3种):
- 1、sh(Bourne Shell):最原始版本shell
- 2、bash(Bourne Again Shell):增强版shell,是各种Linux发行版标准配置的Shell
- 3、zsh: 命令补全功能非常强大,可以补齐路径,补齐命令,补齐参数等
- 内建命令:
- cd、echo、alias、umask、exit等命令即是内建命令,凡是用which命令查不到程序文件所在位置的命令都是内建命令。
- 内建命令没有单独的man手册,要在man手册中查看内建命令,应该
man bash-builtins
- 用户在命令行输入命令后,一般情况下Shell会fork一个子进程并exec该命令,然后父进程切换到后台并等待回收子进程。但是Shell的内建命令例外,并不创建新的进程,但执行结束后也会有一个状态码(Exit Status),0表示成功,非0表示失败,可以使用特殊变量$?读取,比如:
ls echo $?
执行脚本
- 编写一个简单的脚本test.sh:
#! /bin/sh pwd cd .. pwd
-
- 第一行开头#!(称为Shebang),它表示该脚本使用后面指定的解释器/bin/sh解释执行。给这个脚本文件加上可执行权限(chmod a+x test.sh)后,可通过 ./test.sh执行,Shell会fork一个子进程并调用exec执行./test.sh这个程序。
- 也可以通过/bin/sh test.sh去执行,这种方式不需要test.sh文件具有可执行权限
- 如果将命令行下命令用()括号括起来,也会fork出一个子Shell执行小括号中的命令,一行中可以输入由分号;隔开的多个命令,比如:
(pwd;cd..;pwd)
和上面shell脚本的执行结果相同,不会改变cd ..命令改变的是子Shell的PWD,而不会影响到交互式Shell
- 下面3种执行方式不会创建子Shell,而是直接在交互式Shell下执行,会改变交互式Shell的PWD:
-
pwd;cd..;pwd
-
source ./test.sh
-
. ./test.sh
.和source的执行效果一样,注意.和后面有一个空格
-
基本语法
- 变量
- 按照惯例,Shell变量由全大写字母加下划线组成,有两种类型的Shell变量:
- 环境变量
- 环境变量可以从父进程传给子进程,因此Shell进程的环境变量可以从当前Shell进程传给fork出来的子进程。
- env和printenv命令可以显示当前Shell进程的环境变量。
- 本地变量
- 只存在于当前Shell进程
- set命令可以显示当前Shell进程中定义的所有变量(包括本地变量和环境变量)和函数
- 环境变量
- 定义和赋值本地变量:
VARNAME=value
注意等号两边都不能有空格,否则会被Shell解释成命令和命令行参数。
- 定义和导出环境变量---export:
export VARNAME=value
也可以分两步完成:
VARNAME=value export VARNAME
- 删除变量
unset VARNAME
unset命令可以删除已定义的环境变量或本地变量
- 获取变量值
- 用${VARNAME}可以表示它的值,在不引起歧义的情况下也可以用$VARNAME表示它的值
- 比如:
echo $SHELL
注意变量的值均为字符串类型,如果对一个没有定义的变量取值,则值为空
- 按照惯例,Shell变量由全大写字母加下划线组成,有两种类型的Shell变量:
- 文件名代换:
- 通配符:*、?、[]
- *:匹配0个或多个任意字符
- ?:匹配一个任意字符
- [] :匹配方括号中任意一个字符的一次出现
- 通配符:*、?、[]
- 命令代换:``或$()
- ``或$()也是一条命令,Shell先执行该命令,然后将输出结果立刻代换到当前命令行中,例如:
DATE=`date` #DATE=$(date) echo $DATE
- ``或$()也是一条命令,Shell先执行该命令,然后将输出结果立刻代换到当前命令行中,例如:
- 算术代换:$(())
- 用于算术计算,$(())中的Shell变量取值将转换成整数,$(())中只能用+-*/和()运算符,并且只能做整数运算:
VAR=10 echo $VAR+3 #结果:10+3 echo $(($VAR+3)) #结果:13
- 用于算术计算,$(())中的Shell变量取值将转换成整数,$(())中只能用+-*/和()运算符,并且只能做整数运算:
- 转义字符\
- \在Shell中被用作转义字符,用于去除紧跟其后的单个字符的特殊意义(回车除外),换句话说,紧跟其后的字符取字面值。例如:
echo $SHELL #/bin/bash echo \$SHELL #$SHELL touch \$\ \$ #创建文件名'$ $'的文件
- \还有一种用法,在\后敲回车表示续行
- \在Shell中被用作转义字符,用于去除紧跟其后的单个字符的特殊意义(回车除外),换句话说,紧跟其后的字符取字面值。例如:
- 单引号和双引号
- Shell脚本中的单引号和双引号一样都是字符串的界定符
- 单引号用于保持引号内所有字符的字面值
- 双引号中如果有变量,允许变量扩展,与单引号不同
DATE=$(date) echo '$DATE' # $DATE echo "$DATE" #Mon Jul 26 18:02:59 CST 2021 echo "$DATE+10000" #Mon Jul 26 18:02:59 CST 2021+10000
shell脚本语法
- 条件测试:test或[
- 命令test或[可以测试一个条件是否成立,如果测试结果为真,则该命令的Exit Status为0,如果测试结果为假,则命令的Exit Status为1
- 左方括号[确实是一个命令的名字,传给命令的各参数之间应该用空格隔开。命令test或[的参数形式是相同的,只不过test命令不需要]参数。以[命令为例,常见的测试命令如下表所示:
[ -d DIR ] 如果DIR存在并且是一个目录则为真 [ -f FILE ] 如果FILE存在且是一个普通文件则为真 [ -z STRING ] 如果STRING的长度为零则为真 [ -n STRING ] 如果STRING的长度非零则为真 [ STRING1 = STRING2 ] 如果两个字符串相同则为真 [ STRING1 != STRING2 ] 如果字符串不相同则为真 [ ARG1 OP ARG2 ] ARG1和ARG2应该是取值为整数的变量,OP是-eq(等于)-ne(不等于)-lt(小于)-le(小于等于)-gt(大于)-ge(大于等于)之一 [ ! EXPR ] EXPR可以是上表中的任意一种测试条件,!表示逻辑反 [ EXPR1 -a EXPR2 ] EXPR1和EXPR2可以是上表中的任意一种测试条件,-a表示逻辑与 [ EXPR1 -o EXPR2 ] EXPR1和EXPR2可以是上表中的任意一种测试条件,-o表示逻辑或
注意:作为一种好的Shell编程习惯,应该总是把变量取值放在双引号之中。如果变量事先没有定义,则被Shell展开为空字符串,会造成测试条件的语法错误
- 示例:
python@Elite-Wang:~$ VAR='' python@Elite-Wang:~$ [ -z "$VAR" ] python@Elite-Wang:~$ echo $? 0 python@Elite-Wang:~$ unset VAR python@Elite-Wang:~$ VAR1=1 python@Elite-Wang:~$ VAR2=2 python@Elite-Wang:~$ test "$VAR1" -lt "$VAR2" python@Elite-Wang:~$ echo $? 0 python@Elite-Wang:~$ VAR=abc python@Elite-Wang:~$ [ -d downloads -a $VAR = 'abc' ] python@Elite-Wang:~$ echo $? 0 python@Elite-Wang:~$ unset VAR python@Elite-Wang:~$ [ -d downloads -a $VAR = 'abc' ] -bash: [: too many arguments python@Elite-Wang:~$ [ -d downloads -a "$VAR" = 'abc' ] python@Elite-Wang:~$ echo $? 1
- if/then/elif/else/fi
- 在Shell中用if、then、elif、else、fi这几条命令实现分支控制,例如:
#! /bin/sh if [ -f ~/.bashrc ]; then . ~/.bashrc fi
其实是三条命令,if [ -f ~/.bashrc ]是第一条,then . ~/.bashrc是第二条,fi是第三条。如果两条命令写在同一行则需要用;号隔开,一行只写一条命令就不需要写;号了,另外,then后面有换行,但这条命令没写完,Shell会自动续行,把下一行接在then后面当作一条命令处理。和[命令一样,要注意命令和各参数之间必须用空格隔开。if命令的参数组成一条子命令,如果该子命令的Exit Status为0(表示真),则执行then后面的子命令,如果Exit Status非0(表示假),则执行elif、else或者fi后面的子命令。if后面的子命令通常是测试命令,但也可以是其它命令。Shell脚本没有{}括号,所以用fi表示if语句块的结束。
- :(冒号):是一个特殊的命令,称为空命令,该命令不做任何事,但Exit Status总是真。此外,也可以执行/bin/true或/bin/false得到真或假的Exit Status。示例:
#! /bin/bash if [ -f /bin/bash ]; then echo '/bin/bash is a file' else echo '/bin/bash is not a file' fi if :; then echo 'always true' fi
- read命令:作用是等待用户输入一行字符串,将该字符串存到一个Shell变量中,示例:
#! /bin/sh echo "Is it morning? Please answer yes or no." read YES_OR_NO if [ "$YES_OR_NO" = "yes" ]; then echo "Good morning!" elif [ "$YES_OR_NO" = "no" ]; then echo "Good afternoon!" else echo "Sorry, $YES_OR_NO not recognized. Enter yes or no." fi
- 在Shell中用if、then、elif、else、fi这几条命令实现分支控制,例如:
- case/esac
- Shell脚本的case可以匹配字符串和Wildcard,每个匹配分支可以有若干条命令,末尾必须以;;结束,执行时找到第一个匹配的分支并执行相应的命令,然后直接跳到esac之后,esac表示case语句块的结束。例如:
#! /bin/sh echo "Is it morning? Please answer yes or no." read YES_OR_NO case "$YES_OR_NO" in yes|y|Yes|YES) echo "Good Morning!";; [nN]*) echo "Good Afternoon!";; *) echo "Sorry, $YES_OR_NO not recognized. Enter yes or no." exit 1;; esac exit 0
- 使用case语句的例子可以在系统服务的脚本目录/etc/init.d中找到。这个目录下的脚本大多具有这种形式(以/etc/init.d/nfs-kernel-server为例):
case "$1" in start) ... ;; stop) ... ;; reload | force-reload) ... ;; restart) ... *) log_success_msg "Usage: nfs-kernel-server {start|stop|status|reload|force-reload|restart}" exit 1 ;; esac
$1是一个特殊变量,在执行脚本时自动取值为第一个命令行参数
- Shell脚本的case可以匹配字符串和Wildcard,每个匹配分支可以有若干条命令,末尾必须以;;结束,执行时找到第一个匹配的分支并执行相应的命令,然后直接跳到esac之后,esac表示case语句块的结束。例如:
- for/do/done
- for循环,示例:
#! /bin/sh for FRUIT in apple banana pear; do echo "I like $FRUIT" done
FRUIT是一个循环变量,第一次循环$FRUIT的取值是apple,第二次取值是banana,第三次取值是pear
- 在命令行中可以写成一行,命令间用;隔开:
for FILENAME in `ls chap?`; do mv $FILENAME $FILENAME~; done
- for循环,示例:
- while/do/done
- 比如一个验证密码的脚本:
#! /bin/sh echo "Enter password:" read TRY while [ "$TRY" != "secret" ]; do echo "Sorry, try again" read TRY done
- 控制循环次数
#! /bin/sh COUNTER=1 while [ "$COUNTER" -lt 10 ]; do echo "Here we go again" COUNTER=$(($COUNTER+1)) done
- 比如一个验证密码的脚本:
- break和continue
- break跳出整个循环,continue跳过本次循环
- 用户输错五次密码就报错退出:
#! /bin/sh echo 'please input password:' COUNTER=0 while :; do read PASSWD if [ "$PASSWD" = "123456" ]; then echo "login success!" break else COUNTER=$(($COUNTER+1)) if [ "$COUNTER" -eq 5 ]; then echo '5 chances are used up, login fails!' break else echo 'password error,please login again!' fi fi
- 位置参数和特殊变量
- 常用的位置参数和特殊变量
$0 相当于C语言main函数的argv[0] $1、$2... 这些称为位置参数(Positional Parameter) $# 表示参数的个数,注意这里的#后面不表示注释 $@ 表示参数列表"$1" "$2" ...,例如可以用在for循环中的in后面。 $* 表示参数列表"$1" "$2" ...,同上 $? 上一条命令的Exit Status $$ 当前进程号
- shift命令可以左移位置参数,比如shift 3表示原来的$4现在变成$1,原来的$5现在变成$2等等,原来的$1、$2、$3丢弃,$0不移动。不带参数的shift命令相当于shift 1。例如:
#! /bin/sh echo "$#" echo "$@" shift 2 echo "$*" shift echo "$*" echo "$?" echo "$$"
运行结果:
python@Elite-Wang:~$ ./test.sh aa bb cc dd ee ff 6 aa bb cc dd ee ff cc dd ee ff dd ee ff 0 22248
- 常用的位置参数和特殊变量
Shell输入输出
- echo
- echo显示文本行或变量,或者把字符串输入到文件。
- 参数:
- -e 解析转义字符
- -n 不回车换行。默认情况echo回显的内容后面跟一个回车换行。
- 示例:
python@Elite-Wang:~$ echo "hello world\n\n" hello world\n\n python@Elite-Wang:~$ echo -e "hello world\n\n" hello world python@Elite-Wang:~$ echo -n "hello world\n\n" hello world\n\npython@Elite-Wang:~$
- 管道 |
- 可以通过管道把一个命令的输出传递给另一个命令做输入。
ls -ahl | more
ps -aux | grep 'redis'
- 可以通过管道把一个命令的输出传递给另一个命令做输入。
- tee
- tee命令把结果输出到标准输出,另一个副本输出到相应文件。
ls -aux | tee test.txt
- tee命令把结果输出到标准输出,另一个副本输出到相应文件。
- 文件重定向
cmd > file 把标准输出重定向到新文件中 cmd >> file 追加 cmd > file 2>&1 标准出错也重定向到1所指向的file里 cmd >> file 2>&1 cmd < file1 > file2 输入输出都定向到文件里 cmd < &fd 把文件描述符fd作为标准输入 cmd > &fd 把文件描述符fd作为标准输出 cmd < &- 关闭标准输入
文件描述符0表示标准输入,1标准输出,2标准错误
- 函数
- 函数定义中没有参数列表,例如:
#! /bin/sh foo(){ echo "Function foo is called";} echo "-=start=-" foo echo "-=end=-"
注意函数体的左花括号'{'和后面的命令之间必须有空格或换行,如果将最后一条命令和右花括号'}'写在同一行,命令末尾必须有;号。
- Shell函数没有参数列表并不表示不能传参数,调用函数时可以传任意个参数,在函数内同样是用$0、$1、$2等变量来提取参数,函数中的位置参数相当于函数的局部变量,改变这些变量并不会影响函数外面的$0、$1、$2等变量。函数中可以用return命令返回,如果return后面跟一个数字则表示函数的Exit Status。
- 通过脚本创建多个目录,各目录名通过命令行参数传入,示例:
#! /bin/sh is_directory() { if [ ! -d "$1" ]; then return 1 else return 0 fi } for DIR in "$@"; do if is_directory "$DIR"; then echo "$DIR exists" : else echo "create directory $DIR" mkdir "$DIR" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "create directory $DIR fail" fi fi done
注意is_directory()返回0表示真返回1表示假。
- 函数定义中没有参数列表,例如:
Shell脚本调试
- Shell提供了一些用于调试脚本的选项,如下所示:
- -n:读一遍脚本中的命令但不执行,用于检查脚本中的语法错误
- -v:一边执行脚本,一边将执行过的脚本命令打印到标准错误输出
- -x:提供跟踪执行信息,将执行的每一条命令和结果依次打印出来
- 使用这些选项有三种方法:
- 一是在命令行提供参数
sh -x ./test.sh
- 二是在脚本开头提供参数
#! /bin/sh -x
- 三是在脚本中用set命令启用或禁用参数
#! /bin/sh if [ -z "$1" ]; then set -x echo "ERROR: Insufficient Args." exit 1 set +x fi
set -x和set +x分别表示启用和禁用-x参数,这样可以只对脚本中的某一段进行跟踪调试。
- 一是在命令行提供参数
Shell常用工具
- grep
- Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。
- grep正则表达式的Basic规范中字符?+{}|()应解释为普通字符,要表示上述特殊含义则需要加\转义。如果用egrep,或者grep加上-E参数,则不需要转义
- 主要参数:
grep --help [options]主要参数: -c:只输出匹配行的计数。 -i:不区分大小写。 -h:查询多文件时不显示文件名。 -l:查询多文件时只输出包含匹配字符的文件名。 -n:显示匹配行及 行号。 -s:不显示不存在或无匹配文本的错误信息。 -v:显示不包含匹配文本的所有行。 --color=auto :可以将找到的关键词部分加上颜色的显示。
- pattern正则表达式主要参数:
\: 忽略正则表达式中特殊字符的原有含义。 ^:匹配正则表达式的开始行。 $: 匹配正则表达式的结束行。 \<:从匹配正则表达式的行开始。 \>:到匹配正则表达式的行结束。 [ ]:单个字符,如[A]即A符合要求 。 [ - ]:范围,如[A-Z],即A、B、C一直到Z都符合要求 。 .:所有的单个字符。 *:匹配前面字符的个数,长度可以为0或多个。
- grep命令使用简单实例
$ grep 'test' d* 显示所有以d开头的文件中包含 test的行。 $ grep 'test' aa bb cc 显示在aa,bb,cc文件中匹配test的行。 $ grep '[a-z]\{5\}' aa 显示所有包含每个字符串至少有5个连续小写字符的字符串的行。 $ grep 'w\(es\)t.*\1' aa 如果west被匹配,则es就被存储到内存中,并标记为1,然后搜索任意个字符(.*),这些字符后面紧跟着 另外一个es(\1),找到就显示该行。如果用egrep或grep -E,就不用”\”号进行转义,直接写成’w(es)t.*\1′就可以了。
- grep命令使用复杂实例
- 明确要求搜索子目录
grep -r
- 忽略子目录
grep -d skip
- 用于搜索的特殊符号
\< 和 \> 分别标注单词的开始与结尾。 例如: grep man * 会匹配 ‘Batman’、’manic’、’man’等 grep ‘\<man’ * 匹配’manic’和’man’,但不是’Batman’ grep ‘\<man\>’ 只匹配’man’,而不是’Batman’或’manic’等其他的字符串 ‘^’:指匹配的字符串在行首 ‘$’:指匹配的字符串在行 尾
- 明确要求搜索子目录
- find
- 命令一般形式
find pathname -options [-print -exec -ok ...]
- 命令的参数
pathname: find命令所查找的目录路径。例如用.来表示当前目录,用/来表示系统根目录,递归查找。 -print: find命令将匹配的文件输出到标准输出。 -exec: find命令对匹配的文件执行该参数所给出的shell命令。相应命令的形式为'command' { } \;,注意{ }和\;之间的空格。 -ok: 和-exec的作用相同,只不过以一种更为安全的模式来执行该参数所给出的shell命令,在执行每一个命令之前,都会给出提示,让用户来确定是否执行。
- 命令选项
-name 按照文件名查找文件。 -perm 按照文件权限来查找文件。 -prune 使用这一选项可以使find命令不在当前指定的目录中查找,如果同时使用-depth选项,那么-prune将被find命令忽略。 -user 按照文件属主来查找文件。 -group 按照文件所属的组来查找文件。 -mtime -n +n 按照文件的更改时间来查找文件,-n表示文件更改时间距现在n天以内,+n表示文件更改时间距现在n天以前。find命令还有-atime和-ctime 选项,但它们都和-m time选项。 -nogroup 查找无有效所属组的文件,即该文件所属的组在/etc/groups中不存在。 -nouser 查找无有效属主的文件,即该文件的属主在/etc/passwd中不存在。 -newer file1 ! file2 查找更改时间比文件file1新但比文件file2旧的文件。 -type 查找某一类型的文件,诸如: b - 块设备文件。 d - 目录。 c - 字符设备文件。 p - 管道文件。 l - 符号链接文件。 f - 普通文件。 -size n:[c] 查找文件长度为n块的文件,带有c时表示文件长度以字节计。 -depth 在查找文件时,首先查找当前目录中的文件,然后再在其子目录中查找。 -fstype 查找位于某一类型文件系统中的文件,这些文件系统类型通常可以在配置文件/etc/fstab中找到,该配置文件中包含了本系统中有关文件系统的信息。 -mount 在查找文件时不跨越文件系统mount点。 -follow 如果find命令遇到符号链接文件,就跟踪至链接所指向的文件。
- 注意以下三个的区别:
访问:
-amin n 查找系统中最后N分钟访问的文件 -atime n 查找系统中最后n*24小时访问的文件
改变文件状态: -cmin n 查找系统中最后N分钟被改变文件状态的文件 -ctime n 查找系统中最后n*24小时被改变文件状态的文件
改变文件数据: -mmin n 查找系统中最后N分钟被改变文件数据的文件 -mtime n 查找系统中最后n*24小时被改变文件数据的文件 - 使用exec或ok来执行shell命令
- -exec选项后面跟随着所要执行的命令或脚本,然后是一对儿{},一个空格和一个\,最后是一个分号。
find . -type f -exec ls -l {} \;
- -ok是-exec选项的安全模式。它将在对每个匹配到的文件进行操作之前提示你。
$ find . -name "*.conf" -mtime +5 -ok rm { } \; < rm ... ./conf/httpd.conf > ? n
- -exec选项后面跟随着所要执行的命令或脚本,然后是一对儿{},一个空格和一个\,最后是一个分号。
- 命令一般形式
- xargs
- 在使用find命令的-exec选项处理匹配到的文件时, find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制,这样在find命令运行几分钟之后,就会出现 溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是xargs命令的用处所在,特别是与find命令一起使用。
- find命令把匹配到的文件传递给xargs命令,而xargs命令每次只获取一部分文件而不是全部,不像-exec选项那样。这样它可以先处理最先获取的一部分文件,然后是下一批,并如此继续下去。并且,使用xargs命令只有一个进程
- xargs前面和管道 | 联用,后面跟上操作,相当于-exec 操作
find . -perm -7 -print | xargs chmod o-w #回收其它用户的写权限 find . -type f -print | xargs grep "hello" #用grep命令在所有的普通文件中搜索hello这个词
- 利用xargs批量删除redis数据库中的键
redis-cli keys "key*" | xargs redis-cli del
批量删除redis数据库中以key开头的键
- xargs加上'-i'参数后,可以用'{}'代替'|'前面的标准输出,批量设置以"key"开头key的过期时间,可以在终端中可以执行以下命令:
redis-cli keys "key*" | xargs -i redis-cli expire {} 过期时间(单位:秒)
- sed
- 行处理工具
- sed意为流编辑器(Stream Editor),在Shell脚本和Makefile中作为过滤器使用非常普遍,也就是把前一个程序的输出引入sed的输入,经过一系列编辑命令转换为另一种格式输出。
- sed命令行基本格式:
sed option 'script' file1 file2 ... sed option -f scriptfile file1 file2 ...
- 选项含义
--version 显示sed版本。 --help 显示帮助文档。 -n,--quiet,--silent 静默输出,默认情况下,sed程序在所有的脚本指令执行完毕后,将自动打印模式空间中的内容,这些选项可以屏蔽自动打印。 -e script 允许多个脚本指令被执行。 -f script-file, --file=script-file 从文件中读取脚本指令,对编写自动脚本程序来说很棒! -i,--in-place 直接修改源文件,经过脚本指令处理后的内容将被输出至源文件(源文件被修改)慎用!可以使用tee命令重定向到文件中 -l N, --line-length=N 该选项指定l指令可以输出的行长度,l指令用于输出非打印字符。 --posix 禁用GNU sed扩展功能。 -r, --regexp-extended 在脚本指令中使用扩展正则表达式 -s, --separate 默认情况下,sed将把命令行指定的多个文件名作为一个长的连续的输入流。而GNU sed则允许把他们当作单独的文件,这样如正则表达式则不进行跨文件匹配。 -u, --unbuffered 最低限度的缓存输入与输出。
- 以上仅是sed程序本身的选项功能说明,至于具体的脚本指令,这里就简单介绍几个脚本指令操作作为sed程序的例子。
a,append 追加 i,insert 插入 d,delete 删除 s,substitution 替换
- sed命令的格式:
/pattern/action
其中pattern是正则表达式,action是编辑操作。sed程序一行一行读出待处理文件,如果某一行与pattern匹配,则执行相应的action,如果一条命令没有pattern而只有action,这个action将作用于待处理文件的每一行。
- 常用的sed命令:
/pattern/p 打印匹配pattern的行 /pattern/d 删除匹配pattern的行 /pattern/s/pattern1/pattern2/ 查找符合pattern的行,将该行第一个匹配pattern1的字符串替换为pattern2 /pattern/s/pattern1/pattern2/g 查找符合pattern的行,将该行所有匹配pattern1的字符串替换为pattern2
使用p命令需要注意,sed是把待处理文件的内容连同处理结果一起输出到标准输出的,因此p命令表示除了把文件内容打印出来之外还额外打印一遍匹配pattern的行。比如一个文件testfile的内容是
123 abc 456
打印其中包含abc的行
sed '/abc/p' testfile 123 abc abc 456
要想只输出处理结果,应加上-n选项,使用d命令就不需要-n参数了,注意,sed命令不会修改原文件,删除命令只表示某些行不打印输出,而不是从原文件中删去。
- 使用查找替换命令时,可以把匹配pattern1的字符串复制到pattern2中,比如:
$ sed 's/bc/-&-/' testfile 123 a-bc- 456 pattern2中的&表示原文件的当前行中与pattern1相匹配的字符串
- 再比如:
$ sed 's/\([0-9]\)\([0-9]\)/-\1-~\2~/' testfile -1-~2~3 abc -4-~5~6
pattern2中的\1表示与pattern1的第一个()括号相匹配的内容,\2表示与pattern1的第二个()括号相匹配的内容。sed默认使用Basic正则表达式规范,如果指定了-r选项则使用Extended规范,那么()括号就不必转义了。
- 执行多个脚本指令:
$ sed 's/yes/no/;s/static/dhcp/' ./testfile 注:使用分号隔开指令。 $ sed -e 's/yes/no/' -e 's/static/dhcp/' testfile 注:使用-e选项。
- awk
- sed以行为单位处理文件,awk比sed强的地方在于不仅能以行为单位还能以列为单位处理文件。awk缺省的行分隔符是换行,缺省的列分隔符是连续的空格和Tab,但是行分隔符和列分隔符都可以自定义,比如/etc/passwd文件的每一行有若干个字段,字段之间以:分隔,就可以重新定义awk的列分隔符为:并以列为单位处理这个文件。awk实际上是一门很复杂的脚本语言,还有像C语言一样的分支和循环结构,但是基本用法和sed类似,awk命令行的基本形式为:
awk option 'script' file1 file2 ... awk option -f scriptfile file1 file2 ...
- 和sed一样,awk处理的文件既可以由标准输入重定向得到,也可以当命令行参数传入,编辑命令可以直接当命令行参数传入,也可以用-f参数指定一个脚本文件,编辑命令的格式为:
/pattern/{actions} condition{actions}
- 和sed类似,pattern是正则表达式,actions是一系列操作。awk程序一行一行读出待处理文件,如果某一行与pattern匹配,或者满足condition条件,则执行相应的actions,如果一条awk命令只有actions部分,则actions作用于待处理文件的每一行。比如文件testfile的内容表示某商店的库存量:
ProductA 30 ProductB 76 ProductC 55
打印每一行的第二列:
$ awk '{print $2;}' testfile 30 76 55
- 自动变量$1、$2分别表示第一列、第二列等,类似于Shell脚本的位置参数,而$0表示整个当前行。再比如,如果某种产品的库存量低于75则在行末标注需要订货:
$ awk '$2<75 {printf "%s\t%s\n", $0, "REORDER";} $2>=75 {print $0;}' testfile ProductA 30 REORDER ProductB 76 ProductC 55 REORDER
可见awk也有和C语言非常相似的printf函数。awk命令的condition部分还可以是两个特殊的condition-BEGIN和END,对于每个待处理文件,BEGIN后面的actions在处理整个文件之前执行一次,END后面的actions在整个文件处理完之后执行一次。
- awk命令可以像C语言一样使用变量(但不需要定义变量),比如统计一个文件中的空行数
awk '/^ *$/ {x=x+1;} END {print x;}' testfile
- 有些awk变量是预定义的有特殊含义的:
- awk常用的内建变量
FILENAME 当前输入文件的文件名,该变量是只读的 NR 当前行的行号,该变量是只读的,R代表record NF 当前行所拥有的列数,该变量是只读的,F代表field OFS 输出格式的列分隔符,缺省是空格 FS 输入文件的列分融符,缺省是连续的空格和Tab ORS 输出格式的行分隔符,缺省是换行符 RS 输入文件的行分隔符,缺省是换行符
例如打印系统中的用户帐号列表:
awk 'BEGIN {FS=":"} {print $1;}' /etc/passwd
- awk常用的内建变量
- sed以行为单位处理文件,awk比sed强的地方在于不仅能以行为单位还能以列为单位处理文件。awk缺省的行分隔符是换行,缺省的列分隔符是连续的空格和Tab,但是行分隔符和列分隔符都可以自定义,比如/etc/passwd文件的每一行有若干个字段,字段之间以:分隔,就可以重新定义awk的列分隔符为:并以列为单位处理这个文件。awk实际上是一门很复杂的脚本语言,还有像C语言一样的分支和循环结构,但是基本用法和sed类似,awk命令行的基本形式为: