最全面实用的Linux Shell脚本编程知识点总结
更新时间:2024-04-12 04:29:01 阅读量: 综合文库 文档下载
- 最全面实用的均线基本常识推荐度:
- 相关推荐
Shell脚本编程的常识 (这些往往是经常用到,但是各种网络上的材料都语焉不详的东西,个人认为比较有用)
七种文件类型
d 目录 l 符号链接 s 套接字文件 b 块设备文件 c 字符设备文件 p 命名管道文件 - 普通文件
正则表达式
从一个文件或命令输出中抽取或过滤文本时。可使用正则表达式(RE),正则表达式是一些特殊或不很特殊的字符串模式的集合。 基本的元字符集:
^ 只匹配行首。 $ 只匹配行尾。
* 一个单字符后紧跟*,匹配0个或多个此单字符。
[] 匹配[]内字符,可以是一个单字符,也可以是字符序列。可以使 用-来表示[]内范围,如[1-5]等价于[1,2,3,4,5]。
\\ 屏蔽一个元字符的特殊含义,如\\$表示字符$,而不表示匹配行 尾。
. 匹配任意单字符。
pattern\\{n\\} 匹配pattern出现的次数n
pattern\\{n,\\}m匹配pattern出现的次数,但表示次数最少为n
pattern\\{n,m\\} 匹配pattern出现的次数在n与m之间(n,m为0-255) 几个常见的例子:
显示可执行的文件:ls –l | grep …x...x..x 只显示文件夹:ls –l | grep ^d 匹配所有的空行:^$ 匹配所有的单词:[A-Z a-z]* 匹配任一非字母型字符:[^A-Z a-z] 包含八个字符的行:^……..$(8个.)
字符类描述
以下是可用字符类的相当完整的列表: [:alnum:] 字母数字 [a-z A-Z 0-9] [:alpha:] 字母 [a-z A-Z] [:blank:] 空格或制表键 [:cntrl:] 任何控制字符 [:digit:] 数字 [0-9]
[:graph:] 任何可视字符(无空格) [:lower:] 小写 [a-z] [:print:] 非控制字符 [:punct:] 标点字符 [:space:] 空格 [:upper:] 大写 [A-Z]
[:xdigit:] 十六进制数字 [0-9 a-f A-F]
尽可能使用字符类是很有利的,因为它们可以更好地适应非英语 locale(包括某些必需的重音字符等等).
shell的引号类型
shell共有四种引用类型: “ ” 双引号 ‘ ’ 单引号 ` ` 反引号 \\ 反斜线
l “ ” 可引用除$、` 、\\ 、外的任意字符或字符串,“ ”中的变量能够正常显示变量值。
l ‘ ’与“ ”类似,不同在于shell会忽略任何的引用值。
例如: GIRL=‘girl’
echo “The ‘$GIRL’ did well” 则打印:The ‘girl’ did well
l ` `用于设置系统命令的输出到变量,shell会将` `中的内容作为一个系统命令并执行质。 例如:echo `date` 则打印当前的系统时间。 l \\ 用来屏蔽特殊含义的字符:& * + ^ $ ` “ | ? 例如:expr 12 \\* 12 将输出144
变量设置时的不同模式:
valiable_name=value 设置实际值到 variable_name中 valiable_name+value 如果设置了variable_name,则重设其值
valiable_name:?value 如果未设置variable_name,则先显示未定义用户错误信息 valiable_name?value 如果未设置variable_name,则显示系统错误信息 valiable_name:=value 如果未设置variable_name,则设置其值 valiable_name-value 同上,但取值并不设置到variable_name
条件测试
test命令用于测试字符串、文件状态和数字,expr测试和执行数值输出。
Test格式:test condition 或 [ condition ](需要特别注意的是condition的两边都要有一个空格,否则会报错),test命令返回0表示成功。
l 下面将分别描述test的三种测试: n 文件状态测试(常用的) -d 测试是否文件夹 -f 测试是否一般文件 -L 测试是否链接文件 -r 测试文件是否可读 -w 测试文件是否可写 -x 测试文件是否可执行 -s 测试文件是否非空 n 字符串测试
五种格式: test “string”
test string_operator “string”
test “string” string_operator “string” [ string_operator “string” ]
[ “string” string_operator “string” ] 其中string_operator可以为: = 两字符串相等 != 两字符串不等 -z 空串
-n 非空串 n 数值测试
两种格式: “number” number_operator “number”
[ “number” number_operator “number” ] 其中:number_operator 可以为:-eq 、-ne、-gt、-lt、-ge 例如: NUMBER=130
[ “990” –le “995” –a “NUMBER” -gt “133” ] (其中-a表示前后结果相“与”)
l expr命令一般用于整数值,但也可以用于字符串。 n 格式: expr srgument operator operator argument 例如: expr 10 + 10
expr 10 ^ 2 (10的平方) expr $value + 10
n 增量计数――expr在循环中最基本的用法 例如: LOOP=0
LOOP=`expr $LOOP + 1`
n 模式匹配:通过指定的冒号选项计算字符串中的字符数 例如: value=account.doc
expr $value : `\\(.*\\).doc` 输出 account
命令执行顺序
&& 成功执行一个命令后再执行下一个 || 一个命令执行失败后再执行另一个命令
( ) 在当前shell中执行一组命令(格式:(命令1;命令2; ……)) { } 同( )
例如: comet mouth_end || ( echo “hello” | mail dave ;exit ) 如果没有( ),则shell将直接执行最后一个命令(exit)
脚本调试
最有用的调试脚本的工具是echo命令,可以随时打印有关变量或操作的信息,以帮助定位错误。也可使用打印最后状态($?) 命令来判断命令是否成功,这时要注意的是要在执行完要测试的命令后立即输出$?,否则$?将会改变。 Set命令也可以用来辅助脚本测试: Set –n 读命令但是不执行 Set –v 显示读取的所有的行 Set –x 显示所有的命令及其参数
(要关闭set选项,只要把-换成+就可以了,这里有点特殊,要注意一下)
一些常用的小trick
打印一些头信息
command << dilimiter …… …… dilimiter
以分界符号dilimiter中的内容作为命令的标准输入
常用在echo命令中,这样就避免了没输出一行就要使用一个echo命令,同时,输出格式的调整也相应变得简单了。
例如: echo << something_message ************************************************
hello, welcome to use my shell script ************************************************ something_message 将在屏幕上输出:
************************************************
hello, welcome to use my shell script ************************************************
一、利用<<的分解符号性质还可以自动选择菜单或实现自动的ftp传输 也就是利用分解符号的性质自动选择菜单。
例如: ./menu_choose >>output_file 2>&1 < 则自动在执行脚本的过程中一步步作出选择:2,3,Y <<这种性质决定了它是理想的访问数据库的有用工具,可以用它来输入面对数据库提示时所作的各种选择。 创建一个长度为0的空文件 执行 > file_name 命令或 touch file_name 命令。 一些常用的shell变量 $# 传递到脚本的参数个数 $* 以一个单字符串显示所有向脚本传递的参数(可大于9个) $$ 脚本运行的当前进程的ID号 $! 后台运行的最后一个进程的ID号 $@ 与$#相同,但使用时加引号,并在引号中返回每个参数 $- 显示shell使用的当前选项 $? 显示最后命令的退出状态,0表示无错误(这个变量也常常用来打印输出,在脚本调试时标记某个shell命令或某个函数是否正确执行,但是要注意,$?记载的是最近的函数或命令的退出状态,因此打印时应该立即打印以获得正确的信息) $0的使用 在变量中有一种位置变量$n,用来存放函数调用或脚本执行时传入的参数,其中$0表示函数名或脚本名,需要注意的是,这时的脚本名传递的是包含全路径的脚本名。从$1-$9表示传入的第一到第九个参数,这样的参数表示不能多于九个,如果多于九个,可以使用下面将要提到的shift指令来读取。 因为$0存放函数名或脚本名,因此我们可以通过echo $0来输出调用信息,但是,由于存放的是全路径名,我们可以利用一个shell命令来得到脚本名,basename $0 将得到$0中名字的部分,而与之相反的,dirname $0将得到$0中路径的部分。 Shift的运用 用head或tail指令指定查阅的行数 例如:查阅文件前20行: head –20 file_name 查阅文件后10行: tail –10 file_name awk使用规则 awk 是一种很棒的语言。awk 适合于文本处理和报表生成,它还有许多精心设计的特性,允许进行需要特殊技巧程序设计。与某些语言不同,awk 的语法较为常见。它借鉴了某些 语言的一些精华部分,如 C 语言、python 和 bash(虽然在技术上,awk 比 python 和 bash 早创建)。awk 是那种一旦学会了就会成为您战略编码库的主要部分的语言。 第一个 awk 让我们继续,开始使用 awk,以了解其工作原理。在命令行中输入以下命令: $ awk '{ print }' /etc/passwd 您将会见到 /etc/passwd 文件的内容出现在眼前。现在,解释 awk 做了些什么。调用 awk 时,我们指定 /etc/passwd 作为输入文件。执行 awk 时,它依次对 /etc/passwd 中的每一行执行 print 命令。所有输出都发送到 stdout,所得到的结果与与执行catting /etc/passwd完全相同。 现在,解释 { print } 代码块。在 awk 中,花括号用于将几块代码组合到一起,这一点类似于 C 语言。在代码块中只有一条 print 命令。在 awk 中,如果只出现 print 命令,那么将打印当前行的全部内容。 这里是另一个 awk 示例,它的作用与上例完全相同: $ awk '{ print $0 }' /etc/passwd 在 awk 中,$0 变量表示整个当前行,所以 print 和 print $0 的作用完全一样。 如果您愿意,可以创建一个 awk 程序,让它输出与输入数据完全无关的数据。以下是一个示例: $ awk '{ print \ 只要将 \字符串传递给 print 命令,它就会打印空白行。如果测试该脚本,将会发现对于 /etc/passwd 文件中的每一行,awk 都输出一个空白行。再次说明, awk 对输入文件中的每一行都执行这个脚本。以下是另一个示例: $ awk '{ print \运行这个脚本将在您的屏幕上写满 hiya。:) 多个字段 awk 非常善于处理分成多个逻辑字段的文本,而且让您可以毫不费力地引用 awk 脚本中每个独立的字段。以下脚本将打印出您的系统上所有用户帐户的列表: $ awk -F\ 上例中,在调用 awk 时,使用 -F 选项来指定 \作为字段分隔符。awk 处理 print $1 命令时,它会打印出在输入文件中每一行中出现的第一个字段。以下是另一个示例: $ awk -F\以下是该脚本输出的摘录: halt7 operator11 root0 shutdown6 sync5 bin1 ....etc. 如您所见,awk 打印出 /etc/passwd 文件的第一和第三个字段,它们正好分别是用户名和用户标识字段。现在,当脚本运行时,它并不理想 -- 在两个输出字段之间没有空格!如果习惯于使用 bash 或 python 进行编程,那么您会指望 print $1 $3 命令在两个字段之间插入空格。然而,当两个字符串在 awk 程序中彼此相邻时,awk 会连接它们但不在它们之间添加空格。以下命令会在这两个字段中插入空格: $ awk -F\ 以这种方式调用 print 时,它将连接 $1、\和 $3,创建可读的输出。当然,如果需要的话,我们还可以插入一些文本标签: $ awk -F\这将产生以下输出: username: halt uid:7 username: operator uid:11 username: root uid:0 username: shutdown uid:6 username: sync uid:5 username: bin uid:1 ....etc. 外部脚本 将脚本作为命令行自变量传递给 awk 对于小的单行程序来说是非常简单的,而对于多行程序,它就比较复杂。您肯定想要在外部文件中撰写脚本。然后可以向 awk 传递 -f 选项,以向它提供此脚本文件: $ awk -f myscript.awk myfile.in 将脚本放入文本文件还可以让您使用附加 awk 功能。例如,这个多行脚本与前面的单行脚本的作用相同,它们都打印出 /etc/passwd 中每一行的第一个字段: BEGIN { FS=\} { print $1 } 这两个方法的差别在于如何设置字段分隔符。在这个脚本中,字段分隔符在代码自身中指定(通过设置 FS 变量),而在前一个示例中,通过在命令行上向 awk 传递 -F\选项来设置 FS。通常,最好在脚本自身中设置字段分隔符,只是因为这表示您可以少输入一个命令行自变量。我们将在本文的后面详细讨论 FS 变量。 BEGIN 和 END 块 通常,对于每个输入行,awk 都会执行每个脚本代码块一次。然而,在许多编程情况中,可能需要在 awk 开始处理输入文件中的文本之前执行初始化代码。对于这种情况,awk 允许您定义一个 BEGIN 块。我们在前一个示例中使用了 BEGIN 块。因为 awk 在开始处理输入文件之前会执行 BEGIN 块,因此它是初始化 FS(字段分隔符)变量、打印页眉或初始化其它在程序中以后会引用的全局变量的极佳位置。 awk 还提供了另一个特殊块,叫作 END 块。awk 在处理了输入文件中的所有行之后执行这个块。通常,END 块用于执行最终计算或打印应该出现在输出流结尾的摘要信息。 规则表达式和块 awk 允许使用规则表达式,根据规则表达式是否匹配当前行来选择执行独立代码块。以下示例脚本只输出包含字符序列 foo 的那些行: /foo/ { print } 当然,可以使用更复杂的规则表达式。以下脚本将只打印包含浮点数的行: /[0-9]+\\.[0-9]*/ { print } 还有许多其它方法可以选择执行代码块。我们可以将任意一种布尔表达式放在一个代码块之前,以控制何时执行某特定块。仅当对前面的布尔表达式求值为真时,awk 才执行代码块。以下示例脚本输出将输出其第一个字段等于 fred 的所有行中的第三个字段。如果当前行的第一个字段不等于 fred,awk 将继续处理文件而不对当前行执行 print 语句: $1 == \ awk 提供了完整的比较运算符集合,包括 \、\、\、\、\和 \。另外,awk 还提供了 \和 \运算符,它们分别表示“匹配”和“不匹配”。它们的用法是在运算符左边指定变量,在右边指定规则表达式。如果某一行的第五个字段包含字符序列 root,那么以下示例将只打印这一行中的第三个字段: $5 ~ /root/ { print $3 } 条件语句 awk 还提供了非常好的类似于 C 语言的 if 语句。如果您愿意,可以使用 if 语句重写前一个脚本: { if ( $5 ~ /root/ ) { print $3 } } 这两个脚本的功能完全一样。第一个示例中,布尔表达式放在代码块外面。而在第二个示例中,将对每一个输入行执行代码块,而且我们使用 if 语句来选择执行 print 命令。这两个方法都可以使用,可以选择最适合脚本其它部分的一种方法。 以下是更复杂的 awk if 语句示例。可以看到,尽管使用了复杂、嵌套的条件语句,if 语句看上去仍与相应的 C 语言 if 语句一样: { if ( $1 == \ if ( $2 == \ print \ } else { print \ } } else if ($1 == \ print \ } else { print \ } } 使用 if 语句还可以将代码: ! /matchme/ { print $1 $3 $4 } 转换成: { if ( $0 !~ /matchme/ ) { print $1 $3 $4 } } 这两个脚本都只输出不包含 matchme 字符序列的那些行。此外,还可以选择最适合您的代码的方法。它们的功能完全相同。 awk 还允许使用布尔运算符 \(逻辑与)和 \(逻辑或),以便创建更复杂的布尔表达式: ( $1 == \ 这个示例只打印第一个字段等于 foo 且第二个字段等于 bar 的那些行。 数值变量 至今,我们不是打印字符串、整行就是特定字段。然而,awk 还允许我们执行整数和浮点运算。通过使用数学表达式,可以很方便地编写计算文件中空白行数量的脚本。以下就是这样一个脚本: BEGIN { x=0 } /^$/ { x=x+1 } END { print \ 在 BEGIN 块中,将整数变量 x 初始化成零。然后,awk 每次遇到空白行时,awk 将执行 x=x+1 语句,递增 x。处理完所有行之后,执行 END 块,awk 将打印出最终摘要,指出它找到的空白行数量。 字符串化变量 awk 的优点之一就是“简单和字符串化”。我认为 awk 变量“字符串化”是因为所有 awk 变量在内部都是按字符串形式存储的。同时,awk 变量是“简单的”,因为可以对它执行数学操作,且只要变量包含有效数字字符串,awk 会自动处理字符串到数字的转换步骤。要理解我的观点,请研究以下这个示例: x=\ # We just set x to contain the *string* \ x=x+1 # We just added one to a *string* print x # Incidentally, these are comments :) awk 将输出: 2.01 有趣吧!虽然将字符串值 1.01 赋值给变量 x,我们仍然可以对它加一。但在 bash 和 python 中却不能这样做。首先,bash 不支持浮点运算。而且,如果 bash 有“字符串化”变量,它们并不“简单”;要执行任何数学操作,bash 要求我们将数字放到丑陋的 $( ) ) 结构中。如果使用 python,则必须在对 1.01 字符串执行任何数学运算之前,将它转换成浮点值。虽然这并不困难,但它仍是附加的步骤。如果使用 awk,它是全自动的,而那会使我们的代码又好又整洁。如果想要对每个输入行的第一个字段乘方并加一,可以使用以下脚本: { print ($1^2)+1 } 如果做一个小实验,就可以发现如果某个特定变量不包含有效数字,awk 在对数学表达式求值时会将该变量当作数字零处理。 众多运算符 awk 的另一个优点是它有完整的数学运算符集合。除了标准的加、减、乘、除,awk 还允许使用前面演示过的指数运算符 \、模(余数)运算符 \和其它许多从 C 语言中借入的易于使用的赋值操作符。 这些运算符包括前后加减(i++、--foo)、加/减/乘/除赋值运算符( a+=3、b*=2、c/=2.2、d-=6.2)。不仅如此 -- 我们还有易于使用的模/指数赋值运算符(a^=2、b%=4)。 字段分隔符 awk 有它自己的特殊变量集合。其中一些允许调整 awk 的运行方式,而其它变量可以被读取以收集关于输入的有用信息。我们已经接触过这些特殊变量中的一个,FS。前面已经提到过,这个变量让您可以设置 awk 要查找的字段之间的字符序列。我们使用 /etc/passwd 作为输入时,将 FS 设置成 \。当这样做有问题时,我们还可以更灵活地使用 FS。 FS 值并没有被限制为单一字符;可以通过指定任意长度的字符模式,将它设置成规则表达式。如果正在处理由一个或多个 tab 分隔的字段,您可能希望按以下方式设置 FS: FS=\ 以上示例中,我们使用特殊 \规则表达式字符,它表示“一个或多个前一字符”。 如果字段由空格分隔(一个或多个空格或 tab),您可能想要将 FS 设置成以下规则表达式: FS=\ 这个赋值表达式也有问题,它并非必要。为什么?因为缺省情况下,FS 设置成单一空格字符,awk 将这解释成表示“一个或多个空格或 tab”。在这个特殊示例中,缺省 FS 设置恰恰是您最想要的! 复杂的规则表达式也不成问题。即使您的记录由单词 \分隔,后面跟着三个数字,以下规则表达式仍允许对数据进行正确的分析: Cousin Vinnie Vinnie's Auto Shop 300 City Alley Sosueme, OR 76543 要处理这种情况,代码最好考虑每个字段的记录数量,并依次打印每个记录。现在,代码只打印地址的前三个字段。以下就是我们想要的一些代码: 适合具有任意多字段的地址的 address.awk 版本 BEGIN { FS=\ RS=\ ORS=\} { x=1 while ( x print $NF \ } 首先,将字段分隔符 FS 设置成 \,将记录分隔符 RS 设置成 \,这样 awk 可以象以前一样正确分析多行地址。然后,将输出记录分隔符 ORS 设置成 \,它将使 print 语句在每个调用结尾不输出新行。这意味着如果希望任何文本从新的一行开始,那么需要明确写入 print \。 在主代码块中,创建了一个变量 x 来存储正在处理的当前字段的编号。起初,它被设置成 1。然后,我们使用 while 循环(一种 awk 循环结构,等同于 C 语言中的 while 循环),对于所有记录(最后一个记录除外)重复打印记录和 tab 字符。最后,打印最后一个记录和换行;此外,由于将 ORS 设置成 \,print 将不输出换行。程序输出如下,这正是我们所期望的(不算漂亮,但用 tab 定界,以便于导入电子表格): Jimmy the Weasel 100 Pleasant Drive San Francisco, CA 12345 Big Tony 200 Incognito Ave. Suburbia, WA 67890 Cousin Vinnie Vinnie's Auto Shop 300 City Alley Sosueme, OR 76543 循环结构 我们已经看到了 awk 的 while 循环结构,它等同于相应的 C 语言 while 循环。awk 还有 \循环,它在代码块结尾处对条件求值,而不象标准 while 循环那样在开始处求值。它类似于其它语言中的 \循环。以下是一个示例: do...while 示例 { count=1 do { print \ } while ( count != 1 ) } 与一般的 while 循环不同,由于在代码块之后对条件求值,\循环永远都至少执行一次。换句话说,当第一次遇到普通 while 循环时,如果条件为假,将永远不执行该循环。 for 循环 awk 允许创建 for 循环,它就象 while 循环,也等同于 C 语言的 for 循环: for ( initial assignment; comparison; increment ) { code block } 以下是一个简短示例: for ( x = 1; x <= 4; x++ ) { print \} 此段代码将打印: iteration 1 iteration 2 iteration 3 iteration 4 break 和 continue 此外,如同 C 语言一样,awk 提供了 break 和 continue 语句。使用这些语句可以更好地控制 awk 的循环结构。以下是迫切需要 break 语句的代码片断: while 死循环 while (1) { print \} 因为 1 永远代表是真,这个 while 循环将永远运行下去。以下是一个只执行十次的循环: break 语句示例 x=1 while(1) { print \ if ( x == 10 ) { break } x++ } 这里,break 语句用于“逃出”最深层的循环。\使循环立即终止,并继续执行循环代码块后面的语句。 continue 语句补充了 break,其作用如下: x=1 while (1) { if ( x == 4 ) { x++ continue } print \ if ( x > 20 ) { break } x++ } 这段代码打印 \到 \,\除外。如果迭代等于 4,则增加 x 并调用 continue 语句,该语句立即使 awk 开始执行下一个循环迭代,而不执行代码块的其余部分。如同 break 一样,continue 语句适合各种 awk 迭代循环。在 for 循环主体中使用时,continue 将使循环控制变量自动增加。以下是一个等价循环: for ( x=1; x<=21; x++ ) { if ( x == 4 ) { continue } print \} 在 while 循环中时,在调用 continue 之前没有必要增加 x,因为 for 循环会自动增加 x。 数组 如果您知道 awk 可以使用数组,您一定会感到高兴。然而,在 awk 中,数组下标通常从 1 开始,而不是 0: myarray[1]=\myarray[2]=456 awk 遇到第一个赋值语句时,它将创建 myarray,并将元素 myarray[1] 设置成 \。执行了第二个赋值语句后,数组就有两个元素了。 数组迭代 定义之后,awk 有一个便利的机制来迭代数组元素,如下所示: for ( x in myarray ) { print myarray[x] } 这段代码将打印数组 myarray 中的每一个元素。当对于 for 使用这种特殊的 \形式时,awk 将 myarray 的每个现有下标依次赋值给 x(循环控制变量),每次赋值以后都循环一次循环代码。虽然这是一个非常方便的 awk 功能,但它有一个缺点 -- 当 awk 在数组下标之间轮转时,它不会依照任何特定的顺序。那就意味着我们不能知道以上代码的输出是: jim 456 还是: 456 jim 套用 Forrest Gump 的话来说,迭代数组内容就像一盒巧克力 -- 您永远不知道将会得到什么。因此有必要使 awk 数组“字符串化”,我们现在就来研究这个问题。 数组下标字符串化 在我的前一篇文章中,我演示了 awk 实际上以字符串格式来存储数字值。虽然 awk 要执行必要的转换来完成这项工作,但它却可以使用某些看起来很奇怪的代码: a=\b=\c=a+b+3 执行了这段代码后,c 等于 6。由于 awk 是“字符串化”的,添加字符串 \和 \在功能上并不比添加数字 1 和 2 难。这两种情况下,awk 都可以成功执行运算。awk 的“字符串化”性质非常可爱 -- 您可能想要知道如果使用数组的字符串下标会发生什么情况。例如,使用以下代码: myarr[\print myarr[\ 可以预料,这段代码将打印 \。但如果去掉第二个 \下标中的引号,情况又会怎样呢? myarr[\print myarr[1] 猜想这个代码片断的结果比较难。awk 将 myarr[\和 myarr[1] 看作数组的两个独立元素,还是它们是指同一个元素?答案是它们指的是同一个元素,awk 将打印 \Whipple\,如同第一个代码片断一样。虽然看上去可能有点怪,但 awk 在幕后却一直使用数组的字符串下标! 了解了这个奇怪的真相之后,我们中的一些人可能想要执行类似于以下的古怪代码: myarr[\print myarr[\ 这段代码不仅不会产生错误,而且它的功能与前面的示例完全相同,也将打印 \Whipple\!可以看到,awk 并没有限制我们使用纯整数下标;如果我们愿意,可以使用字符串下标,而且不会产生任何问题。只要我们使用非整数数组下标,如 myarr[\, 那么我们就在使用关联数组。从技术上讲,如果我们使用字符串下标,awk 的后台操作并没有什么不同(因为即便使用“整数”下标,awk 还是会将它看作是字符串)。但是,应该将它们称作关联数组 -- 它听起来很酷,而且会给您的上司留下印象。字符串化下标是我们的小秘密。;) 数组工具 谈到数组时,awk 给予我们许多灵活性。可以使用字符串下标,而且不需要连续的数字序列下标(例如,可以定义 myarr[1] 和 myarr[1000],但不定义其它所有元素)。虽然这些都很有用,但在某些情况下,会产生混淆。幸好,awk 提供了一些实用功能有助于使数组变得更易于管理。 首先,可以删除数组元素。如果想要删除数组 fooarray 的元素 1,输入: delete fooarray[1] 而且,如果想要查看是否存在某个特定数组元素,可以使用特殊的 \布尔运算符,如下所示: if ( 1 in fooarray ) { print \It's there.\} else { print \Can't find it.\} 格式化输出 虽然大多数情况下 awk 的 print 语句可以完成任务,但有时我们还需要更多。在那些情况下,awk 提供了两个我们熟知的老朋友 printf() 和 sprintf()。是的,如同其它许多 awk 部件一样,这些函数等同于相应的 C 语言函数。printf() 会将格式化字符串打印到 stdout,而 sprintf() 则返回可以赋值给变量的格式化字符串。如果不熟悉 printf() 和 sprintf(),介绍 C 语言的文章可以让您迅速了解这两个基本打印函数。在 Linux 系统上,可以输入 \来查看 printf() 帮助页面。 以下是一些 awk sprintf() 和 printf() 的样本代码。可以看到,它们几乎与 C 语言完全相同。 x=1 b=\ printf(\myout=(\print myout 此代码将打印: Jim got a 83 on the last test foo-1 字符串函数 awk 有许多字符串函数,这是件好事。在 awk 中,确实需要字符串函数,因为不能象在其它语言(如 C、C++ 和 Python)中那样将字符串看作是字符数组。例如,如果执行以下代码: mystring=\print mystring[3] 将会接收到一个错误,如下所示: awk: string.gawk:59: fatal: attempt to use scalar as array 噢,好吧。虽然不象 Python 的序列类型那样方便,但 awk 的字符串函数还是可以完成任务。让我们来看一下。 首先,有一个基本 length() 函数,它返回字符串的长度。以下是它的使用方法: print length(mystring) 此代码将打印值: 24 好,继续。下一个字符串函数叫作 index,它将返回子字符串在另一个字符串中出现的位置,如果没有找到该字符串则返回 0。使用 mystring,可以按以下方法调用它: print index(mystring,\awk 会打印: 9 让我们继续讨论另外两个简单的函数,tolower() 和 toupper()。与您猜想的一样,这两个函数将返回字符串并且将所有字符分别转换成小写或大写。请注意,tolower() 和 toupper() 返回新的字符串,不会修改原来的字符串。这段代码: print tolower(mystring) print toupper(mystring) print mystring ……将产生以下输出: how are you doing today? HOW ARE YOU DOING TODAY? How are you doing today? 到现在为止一切不错,但我们究竟如何从字符串中选择子串,甚至单个字符?那就是使用 substr() 的原因。以下是 substr() 的调用方法: mysub=substr(mystring,startpos,maxlen) mystring 应该是要从中抽取子串的字符串变量或文字字符串。startpos 应该设置成起始字符位置,maxlen 应该包含要抽取的字符串的最大长度。请注意,我说的是最大长度;如果 length(mystring) 比 startpos+maxlen 短,那么得到的结果就会被截断。substr() 不会修改原始字符串,而是返回子串。以下是一个示例: print substr(mystring,9,3) awk 将打印: you 如果您通常用于编程的语言使用数组下标访问部分字符串(以及不使用这种语言的人),请记住 substr() 是 awk 代替方法。需要使用它来抽取单个字符和子串;因为 awk 是基于字符串的语言,所以会经常用到它。 一些更耐人寻味的函数 首先是 match()。match() 与 index() 非常相似,它与 index() 的区别在于它并不搜索子串,它搜索的是规则表达式。match() 函数将返回匹配的起始位置,如果没有找到匹配,则返回 0。此外,match() 还将设置两个变量,叫作 RSTART 和 RLENGTH。RSTART 包含返回值(第一个匹配的位置),RLENGTH 指定它占据的字符跨度(如果没有找到匹配,则返回 -1)。通过使用 RSTART、RLENGTH、substr() 和一个小循环,可以轻松地迭代字符串中的每个匹配。以下是一个 match() 调用示例: print match(mystring,/you/), RSTART, RLENGTH awk 将打印: 9 9 3 字符串替换 现在,我们将研究两个字符串替换函数,sub() 和 gsub()。这些函数与目前已经讨论过的函数略有不同,因为它们确实修改原始字符串。以下是一个模板,显示了如何调用 sub(): sub(regexp,replstring,mystring) 调用 sub() 时,它将在 mystring 中匹配 regexp 的第一个字符序列,并且用 replstring 替换该序列。sub() 和 gsub() 用相同的自变量;唯一的区别是 sub() 将替换第一个 regexp 匹配(如果有的话),gsub() 将执行全局替换,换出字符串中的所有匹配。以下是一个 sub() 和 gsub() 调用示例: sub(/o/,\print mystring mystring=\gsub(/o/,\print mystring 必须将 mystring 复位成其初始值,因为第一个 sub() 调用直接修改了 mystring。在执行时,此代码将使 awk 输出: HOw are you doing today? HOw are yOu dOing tOday? 当然,也可以是更复杂的规则表达式。我把测试一些复杂规则表达式的任务留给您来完成。 通过介绍函数 split(),我们来汇总一下已讨论过的函数。split() 的任务是“切开”字符串,并将各部分放到使用整数下标的数组中。以下是一个 split() 调用示例: numelements=split(\s,\ 调用 split() 时,第一个自变量包含要切开文字字符串或字符串变量。在第二个自变量中,应该指定 split() 将填入片段部分的数组名称。在第三个元素中,指定用于切开字符串的分隔符。split() 返回时,它将返回分割的字符串元素的数量。split() 将每一个片段赋值给下标从 1 开始的数组,因此以下代码: print mymonths[1],mymonths[numelements] ……将打印: Jan Dec 特殊字符串形式 简短注释 -- 调用 length()、sub() 或 gsub() 时,可以去掉最后一个自变量,这样 awk 将对 $0(整个当前行)应用函数调用。要打印文件中每一行的长度,使用以下 awk 脚本: { print length() } sed使用规则 sed 是很有用(但常被遗忘)的 UNIX 流编辑器。sed是十分强大和小巧的文本流编辑器。使用sed 可以执行字符串替换、创建更大的 sed 脚本以及使用 sed 的附加、插入和更改行命令。在以批处理方式编辑文件或以有效方式创建 shell 脚本来修改现有文件方面,它是十分理想的工具。 sed 示例 sed 通过对输入数据执行任意数量用户指定的编辑操作(“命令”)来工作。sed 是基于行的,因此按顺序对每一行执行命令。然后,sed 将其结果写入标准输出 (stdout),它不修改任何输入文件。 让我们看一些示例。头几个会有些奇怪,因为我要用它们演示 sed 如何工作,而不是执行任何有用的任务。然而,如果您是 sed 新手,那么理解它们是十分重要的。下面是第一个示例: $ sed -e 'd' /etc/services 如果输入该命令,将得不到任何输出。那么,发生了什么?在该例中,用一个编辑命令 'd' 调用 sed。sed 打开 /etc/services 文件,将一行读入其模式缓冲区,执行编辑命令(“删除行”),然后打印模式缓冲区(缓冲区已为空)。然后,它对后面的每一行重复这些步骤。这不会产生输出,因为 \命令除去了模式缓冲区中的每一行! 在该例中,还有几件事要注意。首先,根本没有修改 /etc/services。这还是因为 sed 只读取在命令行指定的文件,将其用作输入 -- 它不试图修改该文件。第二件要注意的事是 sed 是面向行的。'd' 命令不是简单地告诉 sed 一下子删除所有输入数据。相反,sed 逐行将 /etc/services 的每一行读入其称为模式缓冲区的内部缓冲区。一旦将一行读入模式缓冲区,它就执行 'd' 命令,然后打印模式缓冲区的内容(在本例中没有内容)。我将在后面为您演示如何使用地址范围来控制将命令应用到哪些行 -- 但是,如果不使用地址,命令将应用到所有行。第三件要注意的事是括起 'd' 命令的单引号的用法。养成使用单引号来括起 sed 命令的习惯是个好注意,这样可以禁用 shell 扩展。 另一个 sed 示例 下面是使用 sed 从输出流除去 /etc/services 文件第一行的示例: $ sed -e '1d' /etc/services | more 如您所见,除了前面有 '1' 之外,该命令与第一个 'd' 命令十分类似。如果您猜到 '1' 指的是第一行,那您就猜对了。与第一个示例中只使用 'd' 不同的是,这一次使用的 'd' 前面有一个可选的数字地址。通过使用地址,可以告诉 sed 只对某一或某些特定行进行编辑。 地址范围 现在,让我们看一下如何指定地址范围。在本例中,sed 将删除输出的第 1 到 10 行: $ sed -e '1,10d' /etc/services | more 当用逗号将两个地址分开时,sed 将把后面的命令应用到从第一个地址开始、到第二个地址结束的范围。在本例中,将 'd' 命令应用到第 1 到 10 行(包括这两行)。所有其它行都被忽略。 带规则表达式的地址 现在演示一个更有用的示例。假设要查看 /etc/services 文件的内容,但是对查看其中包括的注释部分不感兴趣。如您所知,可以通过以 '#' 字符开头的行在 /etc/services 文件中放置注释。为了避免注释,我们希望 sed 删除以 '#' 开始的行。以下是具体做法: $ sed -e '/^#/d' /etc/services | more 试一下该例,看看发生了什么。您将注意到,sed 成功完成了预期任务。现在,让我们分析发生的情况: 要理解 '/^#/d' 命令,首先需要对其剖析。首先,让我们除去 'd' -- 这是我们前面所使用的同一个删除行命令。新增加的是 '/^#/' 部分,它是一种新的规则表达式地址。规则表达 式地址总是由斜杠括起。它们指定一种 模式,紧跟在规则表达式地址之后的命令将仅适用于正好与该特定模式匹配的行。因此,'/^#/' 是一个规则表达式。(规则表达式的有关规定可以参见本文前面的内容) 例如: $ sed -e '/regexp/d' /path/to/my/test/file | more 这将导致 sed 删除任何匹配的行。 对比如下的命令: $ sed -n -e '/regexp/p' /path/to/my/test/file | more 请注意新的 '-n' 选项,该选项告诉 sed 除非明确要求打印模式空间,否则不这样做。您还会注意到,我们用 'p' 命令替换了 'd' 命令,如您所猜想的那样,这明确要求 sed 打印模式空间。就这样,将只打印匹配部分。 有关地址的更多内容 目前为止,我们已经看到了行地址、行范围地址和 regexp 地址。但是,还有更多的可能。我们可以指定两个用逗号分开的规则表达式,sed 将与所有从匹配第一个规则表达式的第一行开始,到匹配第二个规则表达式的行结束(包括该行)的所有行匹配。 例如,以下命令将打印从包含 \的行开始,并且以包含 \的行结束的文本块: $ sed -n -e '/BEGIN/,/END/p' /my/test/file | more 如果没发现 \,那么将不打印数据。如果发现了 \,但是在这之后的所有行中都没发现 \,那么将打印所有后续行。发生这种情况是因为 sed 面向流的特性 -- 它不知道是否会出现 \。 C 源代码示例 如果只要打印 C 源文件中的 main() 函数,可输入: $ sed -n -e '/main[[:space:]]*(/,/^)/p' sourcefile.c | more 该命令有两个规则表达式 '/main[[:space:]]*(/' 和 '/^}/',以及一个命令 'p'。第一个规则表达式将与后面依次跟有任意数量的空格或制表键以及开始圆括号的字符串 \匹配。这应该与一般 ANSI C main() 声明的开始匹配。 在这个特别的规则表达式中,出现了 '[[:space:]]' 字符类。这只是一个特殊的关键字,它告诉 sed 与 TAB 或空格匹配。如果愿意的话,可以不输入 '[[:space:]]',而输入 '[',然后是空格字母,然后是 -V,然后再输入制表键字母和 ']' -- Control-V 告诉 bash 要插入“真正”的制表键,而不是执行命令扩展。使用 '[[:space:]]' 命令类(特别是在脚本中)会更清楚。 现在看一下第二个 regexp。'/^}' 将与任何出现在新行行首的 '}' 字符匹配。如果代码的格式很好,那么这将与 main() 函数的结束花括号匹配。如果格式不好,则不会正确匹配 -- 这是执行模式匹配任务的一件棘手之事。因为是处于 '-n' 安静方式,所以 'p' 命令还是完成其惯有任务,即明确告诉 sed 打印该行。试着对 C 源文件运行该命令 -- 它应该输出整个 main() { } 块,包括开始的 \和结束的 '}'。 替换 让我们看一下 sed 最有用的命令之一,替换命令。使用该命令,可以将特定字符串或匹配的规则表达式用另一个字符串替换。 下面是该命令最基本用法的示例: $ sed -e 's/foo/bar/' myfile.txt 上面的命令将 myfile.txt 中每行第一次出现的 'foo'(如果有的话)用字符串 'bar' 替换,然后将该文件内容输出到标准输出。请注意,我说的是每行第一次出现,尽管这通常不是您想要的。在进行字符串替换时,通常想执行全局替换。也就是说,要替换每行中的所有出现,如下所示: $ sed -e 's/foo/bar/g' myfile.txt 在最后一个斜杠之后附加的 'g' 选项告诉 sed 执行全局替换。 关于 's///' 替换命令,还有其它几件要了解的事。首先,它是一个命令,并且只是一个命令,在所有上例中都没有指定地址。这意味着,'s///' 还可以与地址一起使用来控制要将命令应用到哪些行,如下所示: $ sed -e '1,10s/enchantment/entrapment/g' myfile2.txt 上例将导致用短语 'entrapment' 替换所有出现的短语 'enchantment',但是只在第一到第十行(包括这两行)上这样做。 $ sed -e '/^$/,/^END/s/hills/mountains/g' myfile3.txt 该例将用 'mountains' 替换 'hills',但是,只从空行开始,到以三个字符 'END' 开始的行结束(包括这两行)的文本块上这样做。 关于 's///' 命令的另一个妙处是 '/' 分隔符有许多替换选项。如果正在执行字符串替换,并且规则表达式或替换字符串中有许多斜杠,则可以通过在 's' 之后指定一个不同的字符来更改分隔符。例如,下例将把所有出现的 /usr/local 替换成 /usr: $ sed -e 's:/usr/local:/usr:g' mylist.txt 在该例中,使用冒号作为分隔符。如果需要在规则表达式中指定分隔符字符,可以在它前面加入反斜杠。 规则表达式混乱 目前为止,我们只执行了简单的字符串替换。虽然这很方便,但是我们还可以匹配规则表达式。例如,以下 sed 命令将匹配从 '<' 开始、到 '>' 结束、并且在其中包含任意数量字符的短语。下例将删除该短语(用空字符串替换): $ sed -e 's/<.*>//g' myfile.html 这是要从文件除去 HTML 标记的第一个很好的 sed 脚本尝试,但是由于规则表达式的特有规则,它不会很好地工作。原因何在?当 sed 试图在行中匹配规则表达式时,它要在行中查找最长的匹配。在我的前一篇 sed 文章中,这不成问题,因为我们使用的是 'd' 和 'p' 命令,这些命令总要删除或打印整行。但是,在使用 's///' 命令时,确实有很大不同,因为规则表达式匹配的整个部分将被目标字符串替换,或者,在本例中,被删除。这意味着,上例将把下行: This is what I meant. 变成:meant. 我们要的不是这个,而是:This is what I meant. 幸运的是,有一种简便方法来纠正该问题。我们不输入“'<' 字符后面跟有一些字符并以 '>' 字符结束”的规则表达式,而只需输入一个“'<' 字符后面跟有任意数量非 '>' 字符并以 '>' 字符结束”的规则表达式。这将与最短、而不是最长的可能性匹配。新命令如下: $ sed -e 's/<[^>]*>//g' myfile.html 在上例中,'[^>]' 指定“非 '>'”字符,其后的 '*' 完成该表达式以表示“零或多个非 '>' 字符”。对几个 html 文件测试该命令,将它们管道输出到 \,然后仔细查看其结果。 更多字符匹配 '[ ]' 规则表达式语法还有一些附加选项。要指定字符范围,只要字符不在第一个或最后一个位置,就可以使用 '-',如下所示: '[a-x]*' 这将匹配零或多个全部为 'a'、'b'、'c'...'v'、'w'、'x' 的字符。另外,可以使用 '[:space:]' 字符类来匹配空格(字符类的相关信息可以参见本文前面部分内容)。 高级替换功能 我们已经看到如何执行简单甚至有些复杂的直接替换,但是 sed 还可以做更多的事。实际上可以引用匹配规则表达式的部分或全部,并使用这些部分来构造替换字符串。作为示例,假设您正在回复一条消息。下例将在每一行前面加上短语 \: $ sed -e 's/.*/ralph said: &/' origmsg.txt 输出如下: ralph said: Hiya Jim, ralph said: ralph said: I sure like this sed stuff! ralph said: 该例的替换字符串中使用了 '&' 字符,该字符告诉 sed 插入整个匹配的规则表达式。因此,可以将与 '.*' 匹配的任何内容(行中的零或多个字符的最大组或整行)插入到替换字符串中的任何位置,甚至多次插入。这非常好,但 sed 甚至更强大。 那些极好的带反斜杠的圆括号 's///' 命令甚至比 '&' 更好,它允许我们在规则表达式中定义区域,然后可以在替换字符串中引用这些特定区域。作为示例,假设有一个包含以下文本的文件: bar oni eeny meeny miny larry curly moe jimmy the weasel 现在假设要编写一个 sed 脚本,该脚本将把 \替换成 \eeny-meeny Von miny\等等。要这样做,首先要编写一个由空格分隔并与三个字符串匹配的规则表达式: '.* .* .*' 现在,将在其中每个感兴趣的区域两边插入带反斜杠的圆括号来定义区域: '\\(.*\\) \\(.*\\) \\(.*\\)' 除了要定义三个可在替换字符串中引用的逻辑区域以外,该规则表达式的工作原理将与第一个规则表达式相同。下面是最终脚本: $ sed -e 's/\\(.*\\) \\(.*\\) \\(.*\\)/Victor \\1-\\2 Von \\3/' myfile.txt 如您所见,通过输入 '\\x'(其中,x 是从 1 开始的区域号)来引用每个由圆括号定界的区域。输入如下: Victor foo-bar Von oni Victor eeny-meeny Von miny Victor larry-curly Von moe Victor jimmy-the Von weasel 随着对 sed 越来越熟悉,您可以花最小力气来进行相当强大的文本处理。您可能想如何使用熟悉的脚本语言来处理这种问题 -- 能用一行代码轻易实现这样的解决方案吗? 组合使用 在开始创建更复杂的 sed 脚本时,需要有输入多个命令的能力。有几种方法这样做。首先,可以在命令之间使用分号。例如,以下命令系列使用 '=' 命令和 'p' 命令,'=' 命令告诉 sed 打印行号,'p' 命令明确告诉 sed 打印该行(因为处于 '-n' 模式)。 $ sed -n -e '=;p' myfile.txt 无论什么时候指定了两个或更多命令,都按顺序将每个命令应用到文件的每一行。在上例中,首先将 '=' 命令应用到第 1 行,然后应用 'p' 命令。接着,sed 继续处理第 2 行,并重复该过程。虽然分号很方便,但是在某些场合下,它不能正常工作。另一种替换方法是使用两个 -e 选项来指定两个不同的命令: $ sed -n -e '=' -e 'p' myfile.txt 然而,在使用更为复杂的附加和插入命令时,甚至多个 '-e' 选项也不能帮我们的忙。对于复杂的多行脚本,最好的方法是将命令放入一个单独的文件中。然后,用 -f 选项引用该脚本文件: $ sed -n -f mycommands.sed myfile.txt 这种方法虽然可能不太方便,但总是管用。 一个地址的多个命令 有时,可能要指定应用到一个地址的多个命令。这在执行许多 's///' 以变换源文件中的字和语法时特别方便。要对一个地址执行多个命令,可在文件中输入 sed 命令,然后使用 '{ }' 字符将这些命令分组,如下所示: 1,20{ s/[Ll]inux/GNU\\/Linux/g s/samba/Samba/g s/posix/POSIX/g } 上例将把三个替换命令应用到第 1 行到第 20 行(包括这两行)。还可以使用规则表达式地址或者二者的组合: 1,/^END/{ s/[Ll]inux/GNU\\/Linux/g s/samba/Samba/g s/posix/POSIX/g p } 该例将把 '{ }' 之间的所有命令应用到从第 1 行开始,到以字母 \开始的行结束(如果在源文件中没发现 \,则到文件结束)的所有行。 附加、插入和更改行 既然在单独的文件中编写 sed 脚本,我们可以利用附加、插入和更改行命令。这些命令将在当前行之后插入一行,在当前行之前插入一行,或者替换模式空间中的当前行。它们也可以用来将多行插入到输出。插入行命令用法如下: i\\ This line will be inserted before each line 如果不为该命令指定地址,那么它将应用到每一行,并产生如下的输出: This line will be inserted before each line line 1 here
正在阅读:
最全面实用的Linux Shell脚本编程知识点总结04-12
如何正确分析判断结构设计电算结果09-29
苏教版科学四年级下册3.3《运动的方式》05-01
技术练兵考试题04-06
调查报告04-02
如何抠取婚纱 - 图文11-14
计算机视觉技术应用_邹庆华06-01
期权从业考试题含答案分04-16
- 多层物业服务方案
- (审判实务)习惯法与少数民族地区民间纠纷解决问题(孙 潋)
- 人教版新课标六年级下册语文全册教案
- 词语打卡
- photoshop实习报告
- 钢结构设计原理综合测试2
- 2014年期末练习题
- 高中数学中的逆向思维解题方法探讨
- 名师原创 全国通用2014-2015学年高二寒假作业 政治(一)Word版
- 北航《建筑结构检测鉴定与加固》在线作业三
- XX县卫生监督所工程建设项目可行性研究报告
- 小学四年级观察作文经典评语
- 浅谈110KV变电站电气一次设计-程泉焱(1)
- 安全员考试题库
- 国家电网公司变电运维管理规定(试行)
- 义务教育课程标准稿征求意见提纲
- 教学秘书面试技巧
- 钢结构工程施工组织设计
- 水利工程概论论文
- 09届九年级数学第四次模拟试卷
- 脚本编程
- 最全面
- 知识点
- 总结
- 实用
- Linux
- Shell
- 《美容应用药物学》课程标准
- 国际贸易作业(配答案)
- 寝室建设
- 法国核电行业管理的考察与思考
- 2014年中南大学全国硕士研究生入学考试《西方经济学》考试大纲
- 铍铜簧片
- 航院公选课大学英语四六级写作笔记 以09年考研小作文为例
- 电子器件元器件生产项目可行性研究报告(-立项备案) - 图文
- 罗丹明B的电催化作用
- 2018年汕头市澄海区初中数学学业模拟考试题(有答案)
- 六年级数学整理复习题
- 江苏省高级人民法院侵犯专利权纠纷案件审理指南 2010
- 中国信息检索系统行业市场前景分析预测年度报告(目录) - 图文
- MPD工艺技术及其在海洋钻井中的应用模式
- 幼儿园小托班保育员工作总结
- 部编版语文一下《小壁虎借尾巴》教学设计
- 液压与气压传动实验
- 《机电传动控制》练习题及答案(1)
- 2016届四川省成都七中高三(下)入学数学试卷(文科)(解析版)
- Flexsim软件实习日志