linux shell 字符串操作详解 (长度,读取,替换,截取,连接,对比,删除,位置 )(转)

 linux, shell  linux shell 字符串操作详解 (长度,读取,替换,截取,连接,对比,删除,位置 )(转)已关闭评论
4月 012021
 

网上看到的一篇关于shell下操作字符串的好文章,linux shell 字符串操作详解 (长度,读取,替换,截取,连接,对比,删除,位置 ),原文地址见文末。

 

1.Linux shell 截取字符变量的前8位

实现方法有如下几种:

  1. expr substr “$a” 1 8
  2. echo $a|awk ‘{print substr(,1,8)}’
  3. echo $a|cut -c1-8
  4. echo $
  5. expr $a : ‘\(.\\).*’
  6. echo $a|dd bs=1 count=8 2>/dev/null

 

2.按指定的字符串截取

(1)第一种方法:

从左向右截取最后一个string后的字符串
${varible##*string}
从左向右截取第一个string后的字符串
${varible#*string}
从右向左截取最后一个string后的字符串
${varible%%string*}
从右向左截取第一个string后的字符串
${varible%string*}
“*”只是一个通配符可以不要

请看下面的例子:

$ MYVAR=foodforthought.jpg
$ echo ${MYVAR##*fo}
rthought.jpg
$ echo ${MYVAR#*fo}
odforthought.jpg

(2)第二种方法:

${varible:n1:n2}:截取变量varible从n1开始的n2个字符,组成一个子字符串。可以根据特定字符偏移和长度,使用另一种形式的变量扩展,来选择特定子字符串。试着在 bash 中输入以下行:

$ EXCLAIM=cowabunga
$ echo ${EXCLAIM:0:3}
cow
$ echo ${EXCLAIM:3:7}
abunga

这种形式的字符串截断非常简便,只需用冒号分开来指定起始字符和子字符串长度。

3.按照指定要求分割:

比如获取后缀名

ls -al | cut -d “.” -f2

小结:shell对应字符串的处理方法很多,根据需求灵活选择。

 

在做shell批处理程序时候,经常会涉及到字符串相关操作。有很多命令语句,如:awk,sed都可以做字符串各种操作。 其实shell内置一系列操作符号,可以达到类似效果,大家知道,使用内部操作符会省略启动外部程序等时间,因此速度会非常的快。

 

一、判断读取字符串值

表达式 含义

${var} 变量var的值, 与$var相同
${var-DEFAULT} 如果var没有被声明, 那么就以$DEFAULT作为其值 *
${var:-DEFAULT} 如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值 *
${var=DEFAULT} 如果var没有被声明, 那么就以$DEFAULT作为其值 *
${var:=DEFAULT} 如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值 *
${var+OTHER} 如果var声明了, 那么其值就是$OTHER, 否则就为null字符串
${var:+OTHER} 如果var被设置了, 那么其值就是$OTHER, 否则就为null字符串
${var?ERR_MSG} 如果var没被声明, 那么就打印$ERR_MSG *
${var:?ERR_MSG} 如果var没被设置, 那么就打印$ERR_MSG *
${!varprefix*} 匹配之前所有以varprefix开头进行声明的变量
${!varprefix@} 匹配之前所有以varprefix开头进行声明的变量

加入了“*”  不是意思是: 当然, 如果变量var已经被设置的话, 那么其值就是$var.

 

 

二、字符串操作(长度,读取,替换)

表达式 含义

${#string} $string的长度
${string:position} 在$string中, 从位置$position开始提取子串
${string:position:length} 在$string中, 从位置$position开始提取长度为$length的子串
${string#substring} 从变量$string的开头, 删除最短匹配$substring的子串
${string##substring} 从变量$string的开头, 删除最长匹配$substring的子串
${string%substring} 从变量$string的结尾, 删除最短匹配$substring的子串
${string%%substring} 从变量$string的结尾, 删除最长匹配$substring的子串
${string/substring/replacement} 使用$replacement, 来代替第一个匹配的$substring
${string//substring/replacement} 使用$replacement, 代替所有匹配的$substring
${string/#substring/replacement} 如果$string的前缀匹配$substring, 那么就用$replacement来代替匹配到的$substring
${string/%substring/replacement} 如果$string的后缀匹配$substring, 那么就用$replacement来代替匹配到的$substring

说明:”* $substring”可以是一个正则表达式.

 

实例:

 

读取:

Java代码  收藏代码
  1. $ echo ${abc-‘ok’}  
  2. ok
  3. $ echo $abc
  4. $ echo ${abc=‘ok’}  
  5. ok
  6. $ echo $abc
  7. ok
  8. #如果abc 没有声明“=” 还会给abc赋值。
  9. $ var1=11;var2=12;var3=  
  10. $ echo ${!v@}
  11. var1 var2 var3
  12. $ echo ${!v*}
  13. var1 var2 var3
  14. #${!varprefix*}与${!varprefix@}相似,可以通过变量名前缀字符,搜索已经定义的变量,无论是否为空值。

 

1,取得字符串长度

C代码  收藏代码
  1. string=abc12342341          //等号二边不要有空格  
  2. echo ${#string}             //结果11  
  3. expr length $string         //结果11  
  4. expr “$string” : “.*”       //结果11 分号二边要有空格,这里的:根match的用法差不多  

2,字符串所在位置

C代码  收藏代码
  1. expr index $string ‘123’    //结果4 字符串对应的下标是从1开始的   
C代码  收藏代码
  1. str=“abc”  
  2. expr index $str “a”  # 1  
  3. expr index $str “b”  # 2  
  4. expr index $str “x”  # 0  
  5. expr index $str “”   # 0   

 

这个方法让我想起来了js的indexOf,各种语言对字符串的操作方法大方向都差不多,如果有语言基础的话,学习shell会很快的。

 

3,从字符串开头到子串的最大长度

C代码  收藏代码
  1. expr match $string ‘abc.*3’ //结果9    

个人觉得这个函数的用处不大,为什么要从开头开始呢。

 

4,字符串截取

C代码  收藏代码
  1. echo ${string:4}      //2342341  从第4位开始截取后面所有字符串    
  2. echo ${string:3:3}    //123      从第3位开始截取后面3位    
  3. echo ${string:3:6}    //123423   从第3位开始截取后面6位    
  4. echo ${string: -4}    //2341  :右边有空格   截取后4位    
  5. echo ${string:(-4)}   //2341  同上    
  6. expr substr $string 3 3   //123  从第3位开始截取后面3位    

 

C代码  收藏代码
  1. str=“abcdef”  
  2. expr substr “$str” 1 3  # 从第一个位置开始取3个字符, abc  
  3. expr substr “$str” 2 5  # 从第二个位置开始取5个字符, bcdef   
  4. expr substr “$str” 4 5  # 从第四个位置开始取5个字符, def  
  5. echo ${str:2}           # 从第二个位置开始提取字符串, bcdef
  6. echo ${str:2:3}         # 从第二个位置开始提取3个字符, bcd
  7. echo ${str:(-6):5}        # 从倒数第二个位置向左提取字符串, abcde
  8. echo ${str:(-4):3}      # 从倒数第二个位置向左提取6个字符, cde

 

上面的方法让我想起了,php的substr函数,后面截取的规则是一样的。

 

5,匹配显示内容

C代码  收藏代码
  1. //例3中也有match和这里的match不同,上面显示的是匹配字符的长度,而下面的是匹配的内容    
  2. expr match $string ‘\([a-c]*[0-9]*\)’  //abc12342341    
  3. expr $string : ‘\([a-c]*[0-9]\)’       //abc1    
  4. expr $string : ‘.*\([0-9][0-9][0-9]\)’ //341 显示括号中匹配的内容    

 

这里括号的用法,是不是根其他的括号用法有相似之处呢,

 

6,截取不匹配的内容

C代码  收藏代码
  1. echo ${string#a*3}     //42341  从$string左边开始,去掉最短匹配子串    
  2. echo ${string#c*3}     //abc12342341  这样什么也没有匹配到    
  3. echo ${string#*c1*3}   //42341  从$string左边开始,去掉最短匹配子串    
  4. echo ${string##a*3}    //41     从$string左边开始,去掉最长匹配子串    
  5. echo ${string%3*1}     //abc12342  从$string右边开始,去掉最短匹配子串    
  6. echo ${string%%3*1}    //abc12     从$string右边开始,去掉最长匹配子串    
C代码  收藏代码
  1. str=“abbc,def,ghi,abcjkl”  
  2. echo ${str#a*c}     # 输出,def,ghi,abcjkl  一个井号(#) 表示从左边截取掉最短的匹配 (这里把abbc字串去掉)
  3. echo ${str##a*c}    # 输出jkl,             两个井号(##) 表示从左边截取掉最长的匹配 (这里把abbc,def,ghi,abc字串去掉)
  4. echo ${str#“a*c”}   # 输出abbc,def,ghi,abcjkl 因为str中没有”a*c”子串  
  5. echo ${str##“a*c”}  # 输出abbc,def,ghi,abcjkl 同理  
  6. echo ${str#*a*c*}   # 空
  7. echo ${str##*a*c*}  # 空
  8. echo ${str#d*f)     # 输出abbc,def,ghi,abcjkl,
  9. echo ${str#*d*f}    # 输出,ghi,abcjkl
  10. echo ${str%a*l}     # abbc,def,ghi  一个百分号(%)表示从右边截取最短的匹配
  11. echo ${str%%b*l}    # a             两个百分号表示(%%)表示从右边截取最长的匹配
  12. echo ${str%a*c}     # abbc,def,ghi,abcjkl

这里要注意,必须从字符串的第一个字符开始,或者从最后一个开始,可以这样记忆, 井号(#)通常用于表示一个数字,它是放在前面的;百分号(%)卸载数字的后面; 或者这样记忆,在键盘布局中,井号(#)总是位于百分号(%)的左边(即前面)  。

 

7,匹配并且替换

C代码  收藏代码
  1. echo ${string/23/bb}   //abc1bb42341  替换一次    
  2. echo ${string//23/bb}  //abc1bb4bb41  双斜杠替换所有匹配    
  3. echo ${string/#abc/bb} //bb12342341   #以什么开头来匹配,根php中的^有点像    
  4. echo ${string/%41/bb}  //abc123423bb  %以什么结尾来匹配,根php中的$有点像   

 

C代码  收藏代码
  1. str=“apple, tree, apple tree”  
  2. echo ${str/apple/APPLE}   # 替换第一次出现的apple
  3. echo ${str//apple/APPLE}  # 替换所有apple  
  4. echo ${str/#apple/APPLE}  # 如果字符串str以apple开头,则用APPLE替换它
  5. echo ${str/%apple/APPLE}  # 如果字符串str以apple结尾,则用APPLE替换它
C代码  收藏代码
  1. $ test=‘c:/windows/boot.ini’  
  2. $ echo ${test/\//\\}  
  3. c:\windows/boot.ini
  4. $ echo ${test//\//\\}  
  5. c:\windows\boot.ini
  6. #${变量/查找/替换值} 一个“/”表示替换第一个,”//”表示替换所有,当查找中出现了:”/”请加转义符”\/”表示。  

8. 比较

C代码  收藏代码
  1. [[ “a.txt” == a* ]]        # 逻辑真 (pattern matching)  
  2. [[ “a.txt” =~ .*\.txt ]]   # 逻辑真 (regex matching)  
  3. [[ “abc” == “abc” ]]       # 逻辑真 (string comparision)   
  4. [[ “11” < “2” ]]           # 逻辑真 (string comparision), 按ascii值比较  

9. 连接

C代码  收藏代码
  1. s1=“hello”  
  2. s2=“world”  
  3. echo ${s1}${s2}   # 当然这样写 $s1$s2 也行,但最好加上大括号
10. 字符串删除

Java代码  收藏代码
  1. $ test=‘c:/windows/boot.ini’  
  2. $ echo ${test#/}
  3. c:/windows/boot.ini
  4. $ echo ${test#*/}
  5. windows/boot.ini
  6. $ echo ${test##*/}
  7. boot.ini
  8. $ echo ${test%/*} 
  9. c:/windows 
  10. $ echo ${test%%/*} 
  11. #${变量名#substring正则表达式}从字符串开头开始配备substring,删除匹配上的表达式。 
  12. #${变量名%substring正则表达式}从字符串结尾开始配备substring,删除匹配上的表达式。 
  13. #注意:${test##*/},${test%/*} 分别是得到文件名,或者目录地址最简单方法。   

转自:https://www.cnblogs.com/gaochsh/p/6901809.html

Linux里使用shell中的$(( ))、$( )、“与${ }及区别

 linux, shell  Linux里使用shell中的$(( ))、$( )、“与${ }及区别已关闭评论
4月 072020
 
转一篇linux下关于“$(( ))、$( )、“与${ }”使用及区别的好文章。来自:https://blog.csdn.net/number_0_0/article/details/73291182
命令替换

在bash中,$( )` `(反引号)都是用来作命令替换的。
命令替换与变量替换差不多,都是用来重组命令行的,先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行。

exp 1

  1. $ echo today is $(date “+%Y-%m-%d”)
  2. today is 2014-07-01
$( )与``

在操作上,这两者都是达到相应的效果,但是建议使用$( ),理由如下:

  • ``很容易与”搞混乱,尤其对初学者来说。
  • 在多层次的复合替换中,``必须要额外的跳脱处理(反斜线),而$( )比较直观。
  • 最后,$( )的弊端是,并不是所有的类unix系统都支持这种方式,但反引号是肯定支持的。

exp 2

  1. # 将cmd1执行结果作为cmd2参数,再将cmd2结果作为cmd3的参数
  2. cmd3 $(cmd2 $(cmd1))
  3. # 如果是用反引号,直接引用是不行的,还需要作跳脱处理
  4. cmd3 `cmd2 \`cmd1\“
${ }变量替换

一般情况下,$var${var}是没有区别的,但是用${ }会比较精确的界定变量名称的范围

  1. $ A=B
  2. $ echo ${A}B
  3. BB

取路径、文件名、后缀
先赋值一个变量为一个路径,如下:
file=/dir1/dir2/dir3/my.file.txt

命令 解释 结果
${file#*/} 拿掉第一条 / 及其左边的字符串 dir1/dir2/dir3/my.file.txt
${file##*/} 拿掉最后一条 / 及其左边的字符串 my.file.txt
${file#*.} 拿掉第一个 . 及其左边的字符串 file.txt
${file##*.} 拿掉最后一个 . 及其左边的字符串 txt
${file%/*} 拿掉最后一条 / 及其右边的字符串 /dir1/dir2/dir3
${file%%/*} 拿掉第一条 / 及其右边的字符串 (空值)
${file%.*} 拿掉最后一个 . 及其右边的字符串 /dir1/dir2/dir3/my.file
${file%%.*} 拿掉第一个 . 及其右边的字符串 /dir1/dir2/dir3/my

记忆方法如下:

  • # 是去掉左边(在键盘上 # 在 $ 之左边)
  • % 是去掉右边(在键盘上 % 在 $ 之右边)
  • 单一符号是最小匹配;两个符号是最大匹配
  • *是用来匹配不要的字符,也就是想要去掉的那部分
  • 还有指定字符分隔号,与*配合,决定取哪部分

取子串及替换

命令 解释 结果
${file:0:5} 提取最左边的 5 个字节 /dir1
${file:5:5} 提取第 5 个字节右边的连续 5 个字节 /dir2
${file/dir/path} 将第一个 dir 提换为 path /path1/dir2/dir3/my.file.txt
${file//dir/path} 将全部 dir 提换为 path /path1/path2/path3/my.file.txt
${#file} 获取变量长度 27

根据状态为变量赋值

命令 解释 备注
${file-my.file.txt} 若 $file 没设定,则使用 my.file.txt 作传回值 空值及非空值不作处理
${file:-my.file.txt} 若 $file 没有设定或为空值,则使用 my.file.txt 作传回值 非空值时不作处理
${file+my.file.txt} 若$file 设为空值或非空值,均使用my.file.txt作传回值 没设定时不作处理
${file:+my.file.txt} 若 $file 为非空值,则使用 my.file.txt 作传回值 没设定及空值不作处理
${file=txt} 若 $file 没设定,则回传 txt ,并将 $file 赋值为 txt 空值及非空值不作处理
${file:=txt} 若 $file 没设定或空值,则回传 txt ,将 $file 赋值为txt 非空值时不作处理
${file?my.file.txt} 若 $file 没设定,则将 my.file.txt 输出至 STDERR 空值及非空值不作处理
${file:?my.file.txt} 若 $file没设定或空值,则将my.file.txt输出至STDERR 非空值时不作处理

tips:
以上的理解在于, 你一定要分清楚 unset 与 null 及 non-null 这三种赋值状态. 一般而言, : 与 null 有关, 若不带 : 的话, null 不受影响, 若带 : 则连 null 也受影响.

数组

  1. A=“a b c def” # 定义字符串
  2. A=(a b c def) # 定义字符数组
命令 解释 结果
${A[@]} 返回数组全部元素 a b c def
${A[*]} 同上 a b c def
${A[0]} 返回数组第一个元素 a
${#A[@]} 返回数组元素总个数 4
${#A[*]} 同上 4
${#A[3]} 返回第四个元素的长度,即def的长度 3
A[3]=xyz 则是将第四个组数重新定义为 xyz
$(( ))与整数运算

bash中整数运算符号

符号 功能
+ – * / 分别为加、减、乘、除
% 余数运算
& | ^ ! 分别为“AND、OR、XOR、NOT”

在 $(( )) 中的变量名称,可于其前面加 $ 符号来替换,也可以不用。

  1. $ a=5;b=7;c=2
  2. $ echo $((a+b*c))
  3. 19
  4. $ echo $(($a+$b*$c))
  5. 19

进制转换
$(( ))可以将其他进制转成十进制数显示出来。用法如下:
echo $((N#xx))
其中,N为进制,xx为该进制下某个数值,命令执行后可以得到该进制数转成十进制后的值。

  1. $ echo $((2#110)) # 二进制转十进制
  2. 6
  3. $ echo $((16#2a)) # 十六进制转十进制
  4. 42
  5. $ echo $((8#11)) # 八进制转十进制
  6. 9

(( ))重定义变量值

  1. $ a=5;b=7
  2. $ ((a++));echo $a
  3. 6
  4. $ ((a–));echo $a
  5. 5
$ ((a<b));echo $? 0

使用(( ))作整数测试时,不要跟[ ]的整数测试搞混乱了。

shell中$(( ))与$( )还有${ }的区别

 开发  shell中$(( ))与$( )还有${ }的区别已关闭评论
1月 222018
 

学习了, 来自:http://blog.csdn.net/tg5156/article/details/19406275

$( )与` `(反引号)

在bash shell中,$( )与` `(反引号)都是用来做命令替换(command substitution)用的。
$ echo the last sunday is $(date -d “last sunday” +%Y-%m-%d)
得到上一星期天的日期

用$( )的理由

1. ` `很容易与’ ‘(单引号)搞混。有时在一些奇怪的字形显示中,两种符号是一模一样的(直竖两点)。
2. 在多层次的复合替换中,` `须要额外的跳脱(\`)处理,而$( )则比较直观。例如:
command1 `command2 `command3` `
原本的意图是在command2 `command3`中先将command3替换出来给command2处理,然后再将结果传给command1 `command2 …`来处理。
然而,真正的结果在命令行中却是分成了`command2`与` `两段。
正确的输入应该如下:
command1 `command2 \`command3\` `
换成$( )则一目了然:
command1 $(command2 $(command3))

$( )的不足

` `基本上可在全部的unix shell中使用,若写成shell script移植性比较高。而$( )并不是每一种shell都能使用。

${ }用来作变量替换

一般情况下,$var与${var}作用相同。但是用${ }会比较精确的界定变量名称的范围,例如:
$ A=B
$ echo $AB
原本是打算先将$A的结果替换出来,然后再补一个B字母于其后,但在命令行上,真正的结果却是只会替换变量名称为AB的值出来。
使用${ }就没问题了:
$ echo ${A}B
BB

${ }的一些特异功能

定义一个变量:
file=/dir1/dir2/dir3/my.file.txt
可以用${ }分别替换获得不同的值:
${file#*/} 拿掉第一个 / 及其左边的字符串:dir1/dir2/dir3/my.file.txt
${file##*/} 拿掉最后一个 / 及其左边的字符串:my.file.txt
${file#*.} 拿掉第一个 . 及其左边的字符串:file.txt
${file##*.} 拿掉最后一个 . 及其左边的字符串:txt
${file%/*} 拿掉最后一个 / 及其右边的字符串:/dir1/dir2/dir3
${file%%/*} 拿掉第一个 / 及其右边的字符串:(空值)
${file%.*} 拿掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file
${file%%.*} 拿掉第一个 . 及其右边的字符串:/dir1/dir2/dir3/my
记忆的方法:
# 去掉左边(键盘上 # 在 $ 的左边)
% 去掉右边(在键盘上 % 在 $ 的右边)
单一符号是最小匹配,两个符号是最大匹配。
${file:0:5} 提取最左边的 5 个字节:/dir1
${file:5:5} 提取第 5 个字节右边的连续 5 个字节:/dir2
也可以对变量值里的字符串作替换:
${file/dir/path} 将第一个 dir 替换为 path:/path1/dir2/dir3/my.file.txt
${file//dir/path} 将全部 dir 替换为 path:/path1/path2/path3/my.file.txt
利用${ }还可针对不同的变量状态赋值(未设定、空值、非空值): 
${file-my.file.txt} 若 $file 未设定,则使用 my.file.txt 作传回值。(空值及非空值时不作处理) 
${file:-my.file.txt} 若 $file 未设定或为空值,则使用 my.file.txt 作传回值。(非空值时不作处理)
${file+my.file.txt} 若 $file 设为空值或非空值,均使用 my.file.txt 作传回值。(未设定时不作处理)
${file:+my.file.txt} 若 $file 为非空值,则使用 my.file.txt 作传回值。(未设定及空值时不作处理)
${file=my.file.txt} 若 $file 未设定,则使用 my.file.txt 作传回值,同时将 $file 赋值为 my.file.txt。 (空值及非空值时不作处理)
${file:=my.file.txt} 若 $file 未设定或为空值,则使用 my.file.txt 作传回值,同时将 $file 赋值为 my.file.txt。 (非空值时不作处理)
${file?my.file.txt} :若 $file 未设定,则将 my.file.txt 输出至 STDERR。(空值及非空值时不作处理)
${file:?my.file.txt} :若 $file 未设定或为空值,则将 my.file.txt 输出至 STDERR。(非空值时不作处理)
以上的理解在于,一定要分清楚 unset 与 null 及 non-null 这三种赋值状态。
一般而言,与 null 有关,若不带 : 的话,null 不受影响,若带 : 则连 null 也受影响。
${#var} 可计算出变量值的长度:
${#file} 可得到 27,/dir1/dir2/dir3/my.file.txt 刚好是 27 个字节。

bash数组(array)处理方法

一般而言,A=”a b c def”只是将 $A 替换为一个单一的字符串,但是改为 A=(a b c def),则是将 $A 定义为数组。
bash的数组替换方法可参考如下方法:
${A[@]} 或 ${A[*]} 得到 a b c def(全部数组)
${A[0]} 得到 a (第一个元素),${A[1]} 第二个…
${#A[@]} 或 ${#A[*]} 得到 4 (数组数量)
${#A[0]} 得到 1 (第一个元素 a 的长度),${#A[3]} 得到 3 (第四个元素 def 的长度)
A[3]=xyz 将第四个元素重新定义为 xyz

$(( ))的用途

用来作整数运算。在 bash 中,$(( ))的整数运算符号大致有这些:
+ – * / 加、减、乘、除
% 余数运算
& | ^ ! AND、OR、XOR、NOT运算
举例:
$ a=5; b=7; c=2
$ echo $((a+b*c))
19
$ echo $(((a+b)/c))
6
$ echo $(((a*b)%c))
1
在$(( ))中的变量名称也可以在其前面加 $ 符号:$(($a+$b*$c))也可以得到 19 的结果。
此外,$(( ))还可以作不同进制(如二进制、八进位、十六进制)运算,只是输出结果皆为十进制而已。
echo $((16#2a)) 结果为 42 (16进位转十进制)
举一个实用的例子:
当前的 umask 是 022,新建文件的权限为:
$ umask 022
$ echo “obase=8; $(( 8#666 & (8#777 ^ 8#$(umask)) ))” | bc
644
事实上,单纯用(( ))也可以重定义变量值,或作testing:
a=5; ((a++)) 将 $a 重定义为 6
a=5; ((a–)) a=4
a=5; b=7; ((a < b)) 会得到 0 (true) 的返回值
常见的用于(( ))的测试符号有以下这些:
< 小于
> 大于
<= 小于或等于
>= 大于或等于
== 等于
!= 不等于

处理Apache日志的Bash脚本

 log  处理Apache日志的Bash脚本已关闭评论
12月 142016
 

来自阮一峰的博客,学习了, 转自:http://www.ruanyifeng.com/blog/2012/01/a_bash_script_of_apache_log_analysis.html

以往,我用的是AWStats日志分析软件。它可以生成很详细的报表,但是不太容易定制,得不到某些想要的信息。所以,我就决定自己写一个Bash脚本,统计服务器的日志,顺便温习一下脚本知识。

事实证明,这件事比我预想的难。虽然最终脚本只有20多行,但花了我整整一天,反复查看手册,确认用法和合适的参数。下面就是我的日志分析脚本,虽然它还不是通用的,但是我相信里面用到的命令,足以满足一般的日志分析需求,同时也是很好的学习Bash的实例。如果下面的每一个命令你都知道,我觉得可以堪称熟练使用Bash了。

一、操作环境

在介绍脚本之前,先讲一下我的服务器环境。

我的网络服务器软件是Apache,它会对每一个http请求留下记录,就像下面这一条:

203.218.148.99 – – [01/Feb/2011:00:02:09 +0800] “GET /blog/2009/11/an_autobiography_of_yang_xianyi.html HTTP/1.1” 200 84058 “http://www.ruanyifeng.com/blog/2009/11/freenomics.html” “Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13”

它的意思是2011年2月1日,IP地址为203.218.148.99的访问者,向服务器请求访问网址/blog/2009/11/an_autobiography_of_yang_xianyi.html。

当天所有的访问记录,组成一个日志。过去一年,一共生成了365个日志文件。它们存放在12个目录中,每一个目录表示一个月(2011-01、2011-02、……2011-12),里面的日志文件依次为www-01.log、www-02.log、……www-31.log(假定该月有31天)。

在不压缩的情况下,365个日志文件加起来,要占掉10GB空间。我的目标就是分析这10GB日志,最后得到一个如下形式的访问量排名:

访问量 网址1
访问量 网址2
访问量 网址3
…… ……

二、为什么要用Bash

很多计算机语言,都可以用来完成这个任务。但是,如果只是简单的日志分析,我觉得Bash脚本是最合适的工具。

主要原因有两个:一是”开发快”,Bash脚本是各种Linux命令的组合,只要知道这些命令怎么用,就可以写脚本,基本上不用学习新的语法,而且它不用编译,直接运行,可以边写边试,对开发非常友好。二是”功能强”,Bash脚本的设计目的,就是为了处理输入和输出,尤其是单行的文本,所以非常合适处理日志文件,各种现成的参数加上管道机制,威力无穷。

前面已经说过,最终的脚本我只用了20多行,处理10GB的日志,20秒左右就得到了结果。考虑到排序的巨大计算量,这样的结果非常令人满意,充分证明了Bash的威力。

三、总体思路

我的总体处理思路是这样的:

第一步,处理单个日志。统计每一天各篇文章的访问量。

第二步,生成月度排名。将每一天的统计结果汇总,得到月度访问量。

第三步,生成年度排名。将12个月的统计结果汇总,进行年度访问量的排序。

四、处理单个日志

以2011年1月1日的日志为例,它在目录2011-01之中,文件名是www-01.log,里面有10万条如下格式的记录:

203.218.148.99 – – [01/Feb/2011:00:02:09 +0800] “GET /blog/2009/11/an_autobiography_of_yang_xianyi.html HTTP/1.1” 200 84058 “http://www.ruanyifeng.com/blog/2009/11/freenomics.html” “Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13”

处理这个日志,我只用了一行代码:

awk ‘$9 == 200 {print $7}’ www-01.log | grep -i ‘^/blog/2011/.*\.html$’ | sort | uniq -c | sed ‘s/^ *//g’ > www-01.log.result

它用管道连接了5个命令,每一个都很简单,我们依次来看:

(1) awk ‘$9 == 200 {print $7}’ www-01.log

awk命令默认用空格,将每一行文本分割成若干个字段。仔细数一下,我们需要的只是第7个字段,即http请求的网址,{print $7}表示将第7个字段输出,结果就是:

/blog/2009/11/an_autobiography_of_yang_xianyi.html

考虑到我们只统计成功的请求,因此再加一个限制条件,服务器的状态代码必须是200(表示成功),写成”$9 == 200″,即第9个字段必须是200,否则不输出第7个字段。

更精细的统计,还应该区分网络蜘蛛和真实访问者,由于我想不出简单的分辨方法,这里只好忽略了。

(2)grep -i ‘^/blog/2011/.*\.html$’

在输出的所有记录的第7个字段之中,并不是每一条记录都需要统计的。根据我的文章的命名特点,它们的网址应该都以”/blog/2011/”开头,以”.html”结尾。所以,我用一个正则表达式”^/blog/2011/.*\.html$”,找出这些记录。参数i表示不区分大小写。

(3)sort

这时,所有需要统计的记录应该都列出来了,但是它们的次序是杂乱的。接着,使用sort命令,不过目的不是为了排序,而是把相同的网址排列在一起,为后面使用uniq命令创造条件。

(4)uniq -c

uniq的作用是过滤重复的记录,只保留一行。c参数的作用,是在每行的开头添加该记录的出现次数。处理之后的输出应该是这样的:

32 /blog/2011/01/guidelines_for_english_translations_in_public_places.html
32 /blog/2011/01/api_for_google_s_url_shortener.html
30 /blog/2011/01/brief_history_of_arm.html

它表示以上三篇文章,在1月1日的日志中,分别有32条、32条、30条的访问记录(即访问次数)。

(5)sed ‘s/^ *//g’ > www-01.log.result

上一步uniq命令添加的访问次数,是有前导空格的。也就是说,在上例的32、32、30之前有一连串空格,为了后续操作的方便,这里把前导空格删去。sed命令是一个处理行文本的编辑器,’s/^ *//g’是一个正则表达式(^和*之间有一个空格),表示将行首的连续空格替换为空(即删除)。接着,将排序结果重定向到文件www-01.result。单个日志分析就完成了。

五、月度汇总排名

经过上一步之后,1月份的31个日志文件,生成了31个对应的分析结果文件。为了汇总整个月的情况,必须把这31个结果文件合并。

(6)合并分析结果

for i in www-*.log.result 
do 
cat $i >> log.result
done

这是一个循环结构,把所有www-01.log.result形式的文件,都写进log.result文件。

然后,我用一行语句,计算月度排名。

sort -k2 log.result | uniq -f1 –all-repeated=separate |./log.awk |sort -rn > final.log.result

这行语句由3个命令和1个awk脚本组成:

(7)sort -k2 log.result

由于是31个文件汇总,log.result文件里面的记录是无序的,必须用sort命令,将相同网址的记录归类在一起。但是此时,访问次数是第一个字段,网址是第二个字段,因此参数k2表示根据第二个字段进行排序。

(8)uniq -f1 –all-repeated=separate

uniq的作用是过滤重复的记录,参数f1表示忽略第一个字段(访问次数),只考虑后面的字段(网址);参数表示all-repeated=separate,表示过滤掉所有只出现一次的记录,保留所有重复的记录,并且每一组之间用一个空行分隔。这一步完成以后,输出结果变成如下的形式:

617 /blog/2011/01/guidelines_for_english_translations_in_public_places.html
455 /blog/2011/01/guidelines_for_english_translations_in_public_places.html

223 /blog/2011/01/2010_my_blogging_summary.html
253 /blog/2011/01/2010_my_blogging_summary.html

相同网址都归在一组,组间用空行分割。为了简洁,上面的例子每一组只包含两条记录,实际上每一组都包含31条记录(分别代表当月每天的访问次数)。

(9)log.awk脚本

为了将31天的访问次数加总,我动了很多脑筋。最后发现,唯一的方法就是用awk命令,而且必须另写一个awk脚本。

#!/usr/bin/awk -f

BEGIN {
RS=”” #将多行记录的分隔符定为一个空行
}

{
sum=0 #定义一个表示总和的变量,初值为0
for(i=1;i<=NF;i++){ #遍历所有字段
if((i%2)!=0){ #判断是否为奇数字段
sum += $i #如果是的话,累加这些字段的值
}
}
print sum,$2 #输出总和,后面跟上对应的网址 
}

我已经对上面这个log.awk脚本加了详细注释。这里再说明几点:首先,默认情况下,awk将”\n”作为记录的分隔符,设置RS=””表示改为将空行作为分隔符,因此形成了一个多行记录;其次,NF是一个awk的内置变量,表示当前行的字段总数。由于输入文件之中,每一行都包含两个字段,第一个是访问数,第二个是网址,所以这里做一个条件判断,只要是奇数字段就累加,偶数字段则一律跳过。最后,每个记录输出一个累加值和网址,它们之间用空格分割。

(10)sort -rn > final.log.result

对awk脚本的处理结果进行排序,sort默认使用第一个字段,参数r表示逆序,从大往小排;参数n表示以数值形式排序,不以默认的字典形式排序,否则会出现10小于2的结果。排序结果重定向到final.log.result。至此,月度排名完成。

六、脚本文件

用一个脚本,包含上面两节所有的内容。

#!/bin/bash

if ls ./*.result &> /dev/null #判断当前目录中是否有后缀名为result的文件存在
then
rm *.result #如果有的话,删除这些文件
fi

touch log.result #创建一个空文件

for i in www-*.log #遍历当前目录中所有log文件
do 
echo $i … #输出一行字,表示开始处理当前文件
awk ‘$9 == 200 {print $7}’ $i|grep -i ‘^/blog/2011/.*\.html$’|sort|uniq -c|sed ‘s/^ *//g’ > $i.result #生成当前日志的处理结果
cat $i.result >> log.result #将处理结果追加到log.result文件
echo $i.result finished #输出一行字,表示结束处理当前文件
done

echo final.log.result … #输出一行字,表示最终统计开始

sort -k2 log.result | uniq -f1 –all-repeated=separate |./log.awk |sort -rn > final.log.result #生成最终的结果文件final.log.result

echo final.log.result finished #输出一行字,表示最终统计结束

shell中标准输出,标准错误>/dev/null 2>&1解释

 linux  shell中标准输出,标准错误>/dev/null 2>&1解释已关闭评论
8月 252016
 
shell中可能经常能看到:>/dev/null  2>&1

eg:sudo kill -9 `ps -elf |grep -v grep|grep $1|awk ‘{print $4}’` 1>/dev/null 2>/dev/null

命令的结果可以通过%>的形式来定义输出
/dev/null 代表空设备文件
> 代表重定向到哪里,例如:echo “123” > /home/123.txt
1 表示stdout标准输出,系统默认值是1,所以”>/dev/null”等同于”1>/dev/null”
2 表示stderr标准错误
& 表示等同于的意思,2>&1,表示2的输出重定向等同于1
那么本文标题的语句:
1>/dev/null 首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,说白了就是不显示任何信息。
2>&1 接着,标准错误输出重定向等同于 标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。

/////////////////////

脚本只使用标准输入、标准输出和标准错误
    Shell会自动为我们打开和关闭0、1、2这三个文件描述符,我们不需要显式地打开或关闭它们。标准输入是命令的输入,默认指向键盘;标准输出是命令的输出,默认指向屏幕;标准错误是命令错误信息的输出,默认指向屏幕。
    如果没有显式地进行重定向,命令通过文件描述符0从屏幕读取输入,通过文件描述符1和2将输出和错误信息输出到屏幕。但如果我们想从其他文件(再次强调,I/O设备在Unix/Linux中也是文件)读取输入或产生输出,就需要对0、1、2使用重定向了。其语法如下:

command < filename                         把标准输入重定向到filename文件中
command 0< filename                       把标准输入重定向到filename文件中
command > filename                         把标准输出重定向到filename文件中(覆盖)
command 1> fielname                       把标准输出重定向到filename文件中(覆盖)
command >> filename                       把标准输出重定向到filename文件中(追加)
command 1>> filename                     把标准输出重定向到filename文件中(追加)
command 2> filename                       把标准错误重定向到filename文件中(覆盖)
command 2>> filename                     把标准输出重定向到filename文件中(追加)
command > filename 2>&1               把标准输出和标准错误一起重定向到filename文件中(覆盖)
command >> filename 2>&1             把标准输出和标准错误一起重定向到filename文件中(追加)
command < filename >filename2        把标准输入重定向到filename文件中,把标准输出重定向

                                                        到filename2文件中
command 0< filename 1> filename2   把标准输入重定向到filename文件中,把标准输出重定向

                                                        到filename2文件中
重定向的使用有如下规律:
1)标准输入0、输出1、错误2需要分别重定向,一个重定向只能改变它们中的一个。
2)标准输入0和标准输出1可以省略。(当其出现重定向符号左侧时)
3)文件描述符在重定向符号左侧时直接写即可,在右侧时前面加&。
4)文件描述符与重定向符号之间不能有空格!

linux shell自定义函数(定义、返回值、变量作用域)介绍

 linux  linux shell自定义函数(定义、返回值、变量作用域)介绍已关闭评论
3月 302016
 

学习下shell中的自定义函数:

linux shell 可以用户定义函数,然后在shell脚本中可以随便调用。下面说说它的定义方法,以及调用需要注意那些事项。

一、定义shell函数(define function)

语法:

[ function ] funname [()]

{

action;

[return int;]

}

说明:

  • 1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
  • 2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255

实例(testfun1.sh):

#!/bin/sh
 
 fSum 3 2;
 functionfSum()
 {
   echo$1,$2;
   return$(($1+$2));
 }
 fSum 5 7;
 total=$(fSum 3 2);
 echo$total,$?;
 
sh testfun1.sh
testfun1.sh: line 3: fSum:commandnot found
5,7
3,2
1
5


从上面这个例子我们可以得到几点结论:

  • 1、必须在调用函数地方之前,声明函数,shell脚本是逐行运行。不会像其它语言一样先预编译。一次必须在使用函数前先声明函数。
  • 2、total=$(fSum 3 2); 通过这种调用方法,我们清楚知道,在shell 中 单括号里面,可以是:命令语句。 因此,我们可以将shell中函数,看作是定义一个新的命令,它是命令,因此 各个输入参数直接用 空格分隔。 一次,命令里面获得参数方法可以通过:$0…$n得到。 $0代表函数本身。
  • 3、函数返回值,只能通过$? 系统变量获得,直接通过=,获得是空值。其实,我们按照上面一条理解,知道函数是一个命令,在shell获得命令返回值,都需要通过$?获得。

二、函数作用域,变量作用范围

先我们看一个实例(testfun2.sh ):

#!/bin/sh
 
echo$(uname);
declarenum=1000;
 
uname()
{
  echo”test!”;
  ((num++));
  return100;
}
testvar()
{
  localnum=10;
  ((num++));
  echo$num;
 
}
 
uname;
echo$?
echo$num;
testvar;
echo$num;
 
sh testfun2.sh
Linux
test!
100
1001
11
1001

我们一起来分析下上面这个实例,可以得到如下结论:

  • 1、定义函数可以与系统命令相同,说明shell搜索命令时候,首先会在当前的shell文件定义好的地方查找,找到直接执行。
  • 2、需要获得函数值:通过$?获得
  • 3、如果需要传出其它类型函数值,可以在函数调用之前,定义变量(这个就是全局变量)。在函数内部就可以直接修改,然后在执行函数就可以读出修改过的值。
  • 4、如果需要定义自己变量,可以在函数中定义:local 变量=值 ,这时变量就是内部变量,它的修改,不会影响函数外部相同变量的值 。

转自:http://www.jb51.net/article/33899.htm

linux shell “(())” 双括号运算符使用

 linux  linux shell “(())” 双括号运算符使用已关闭评论
3月 302016
 

在刚开始学习inux shell脚本编程时候,对于它的 四则运算以及逻辑运算。估计很多朋友都感觉比较难以接受。特变逻辑运算符”[]”使用时候,必须保证运算符与算数 之间有空格。 四则运算也只能借助:let,expr等命令完成。 今天讲的双括号”(())”结构语句,就是对shell中算数及赋值运算的扩展。

 

使用方法:

语法:

((表达式1,表达式2…))

特点:

1、在双括号结构中,所有表达式可以像c语言一样,如:a++,b–等。

2、在双括号结构中,所有变量可以不加入:“$”符号前缀。

3、双括号可以进行逻辑运算,四则运算

4、双括号结构 扩展了for,while,if条件测试运算

5、支持多个表达式运算,各个表达式之间用“,”分开

使用实例:

  • 扩展四则运算
1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh
 
a=1;
b=2;
c=3;
 
((a=a+1));
echo $a;
 
a=$((a+1,b++,c++));
echo $a,$b,$c

 

运行结果:

sh testsh.sh
2
3,3,4

双括号结构之间支持多个表达式,然后加减乘除等c语言常用运算符都支持。如果双括号带:$,将获得表达式值,赋值给左边变量。

  • 扩展逻辑运算
1
2
3
4
5
6
7
8
9
10
#!/bin/sh
 
a=1;
b=”ab”;
 
 
echo $((a>1?8:9));
 
((b!=”a”))&&echo “err2”;
((a<2))&&echo “ok”;

 

运行结果:

sh testsh.sh
9
err2
ok

  • 扩展流程控制语句(逻辑关系式)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/sh
 
num=100;
total=0;
 
for((i=0;i<=num;i++));
do
    ((total+=i));
done
echo $total;
 
total=0;
i=0;
while((i<=num));
do
    ((total+=i,i++));
done
echo $total;
 
if((total>=5050));then
    echo “ok”;
fi

 

运算结果:

sh testsh.sh
5050
5050
ok

 

有了双括号运算符:[[]],[],test 逻辑运算,已经let,expr 都可以抛到一边了。

转自:http://www.cnblogs.com/chengmo/archive/2010/10/19/1855577.html

shell中文件的判断(-d,-a, -e等)

 linux  shell中文件的判断(-d,-a, -e等)已关闭评论
3月 282016
 

[ -a FILE ] 如果 FILE 存在则为真。

[ -b FILE ] 如果 FILE 存在且是一个块特殊文件则为真。

[ -c FILE ] 如果 FILE 存在且是一个字特殊文件则为真。

[ -d FILE ] 如果 FILE 存在且是一个目录则为真。

[ -e FILE ] 如果 FILE 存在则为真。

[ -f FILE ] 如果 FILE 存在且是一个普通文件则为真。

[ -g FILE ] 如果 FILE 存在且已经设置了SGID则为真。

[ -h FILE ] 如果 FILE 存在且是一个符号连接则为真。

[ -k FILE ] 如果 FILE 存在且已经设置了粘制位则为真。

[ -p FILE ] 如果 FILE 存在且是一个名字管道(F如果O)则为真。

[ -r FILE ] 如果 FILE 存在且是可读的则为真。

[ -s FILE ] 如果 FILE 存在且大小不为0则为真。

[ -t FD ] 如果文件描述符 FD 打开且指向一个终端则为真。

[ -u FILE ] 如果 FILE 存在且设置了SUID (set user ID)则为真。

[ -w FILE ] 如果 FILE 如果 FILE 存在且是可写的则为真。

[ -x FILE ] 如果 FILE 存在且是可执行的则为真。

[ -O FILE ] 如果 FILE 存在且属有效用户ID则为真。

[ -G FILE ] 如果 FILE 存在且属有效用户组则为真。

[ -L FILE ] 如果 FILE 存在且是一个符号连接则为真。

[ -N FILE ] 如果 FILE 存在 and has been mod如果ied since it was last read则为真。

[ -S FILE ] 如果 FILE 存在且是一个套接字则为真。

[ FILE1 -nt FILE2 ] 如果 FILE1 has been changed more recently than FILE2, or 如果 FILE1 exists and FILE2 does not则为真。

[ FILE1 -ot FILE2 ] 如果 FILE1 比 FILE2 要老, 或者 FILE2 存在且 FILE1 不存在则为真。

[ FILE1 -ef FILE2 ] 如果 FILE1 和 FILE2 指向相同的设备和节点号则为真。

[ -o OPTIONNAME ] 如果 shell选项 “OPTIONNAME” 开启则为真。

[ -z STRING ] “STRING” 的长度为零则为真。

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

[ STRING1 == STRING2 ] 如果2个字符串相同。 “=” may be used instead of “==” for strict POSIX compliance则为真。

[ STRING1 != STRING2 ] 如果字符串不相等则为真。

[ STRING1 < STRING2 ] 如果 “STRING1” sorts before “STRING2” lexicographically in the current locale则为真。

[ STRING1 > STRING2 ] 如果 “STRING1” sorts after “STRING2” lexicographically in the current locale则为真。

[ ARG1 OP ARG2 ] “OP” is one of -eq, -ne, -lt, -le, -gt or -ge. These arithmetic binary operators return true if “ARG1” is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to “ARG2”, respectively. “ARG1” and “ARG2” are integers.

Linux命令之sort

 linux  Linux命令之sort已关闭评论
3月 102015
 

网上看到的关于linux命令使用的好文章,这篇是关于sort,分享下:

sort是在Linux里非常常用的一个命令,管排序的,集中精力,五分钟搞定sort,现在开始!

1 sort的工作原理

sort将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。

[[email protected] programming]$ cat seq.txt
banana
apple
pear
orange
[[email protected] programming]$ sort seq.txt
apple
banana
orange
pear

2 sort的-u选项

它的作用很简单,就是在输出行中去除重复行。

[[email protected] programming]$ cat seq.txt
banana
apple
pear
orange
pear
[[email protected] programming]$ sort seq.txt
apple
banana
orange
pear
pear
[[email protected] programming]$ sort -u seq.txt
apple
banana
orange
pear

pear由于重复被-u选项无情的删除了。

3 sort的-r选项

sort默认的排序方式是升序,如果想改成降序,就加个-r就搞定了。

[[email protected] programming]$ cat number.txt
1
3
5
2
4
[[email protected] programming]$ sort number.txt
1
2
3
4
5
[[email protected] programming]$ sort -r number.txt
5
4
3
2
1

4 sort的-o选项

由于sort默认是把结果输出到标准输出,所以需要用重定向才能将结果写入文件,形如sort filename > newfile。

但是,如果你想把排序结果输出到原文件中,用重定向可就不行了。

[rocr[email protected] programming]$ sort -r number.txt > number.txt
[[email protected] programming]$ cat number.txt
[[email protected] programming]$
看,竟然将number清空了。

就在这个时候,-o选项出现了,它成功的解决了这个问题,让你放心的将结果写入原文件。这或许也是-o比重定向的唯一优势所在。

[[email protected] programming]$ cat number.txt
1
3
5
2
4
[ro[email protected] programming]$ sort -r number.txt -o number.txt
[[email protected] programming]$ cat number.txt
5
4
3
2
1

5 sort的-n选项

你有没有遇到过10比2小的情况。我反正遇到过。出现这种情况是由于排序程序将这些数字按字符来排序了,排序程序会先比较1和2,显然1小,所以就将10放在2前面喽。这也是sort的一贯作风。

我们如果想改变这种现状,就要使用-n选项,来告诉sort,“要以数值来排序”!

[[email protected] programming]$ cat number.txt
1
10
19
11
2
5
[[email protected] programming]$ sort number.txt
1
10
11
19
2
5
[[email protected] programming]$ sort -n number.txt
1
2
5
10
11
19

6 sort的-t选项和-k选项

如果有一个文件的内容是这样:

[[email protected] programming]$ cat facebook.txt
banana:30:5.5
apple:10:2.5
pear:90:2.3
orange:20:3.4

这个文件有三列,列与列之间用冒号隔开了,第一列表示水果类型,第二列表示水果数量,第三列表示水果价格。

那么我想以水果数量来排序,也就是以第二列来排序,如何利用sort实现?

幸好,sort提供了-t选项,后面可以设定间隔符。(是不是想起了cut和paste的-d选项,共鸣~~)

指定了间隔符之后,就可以用-k来指定列数了。

[[email protected] programming]$ sort -n -k 2 -t : facebook.txt
apple:10:2.5
orange:20:3.4
banana:30:5.5
pear:90:2.3

我们使用冒号作为间隔符,并针对第二列来进行数值升序排序,结果很令人满意。

7 其他的sort常用选项

-f会将小写字母都转换为大写字母来进行比较,亦即忽略大小写

-c会检查文件是否已排好序,如果乱序,则输出第一个乱序的行的相关信息,最后返回1

-C会检查文件是否已排好序,如果乱序,不输出内容,仅返回1

-M会以月份来排序,比如JAN小于FEB等等

-b会忽略每一行前面的所有空白部分,从第一个可见字符开始比较。

转自:http://roclinux.cn/?p=1350

xargs使用

 linux  xargs使用已关闭评论
5月 192014
 

使用xargs真是太方便了,批量替换不再是问题,哈哈。

大多数 Linux 命令都会产生输出:文件列表、字符串列表等。但如果要使用其他某个命令并将前一个命令的输出作为参数该怎么办?例如,file 命令显示文件类型(可执行文件、ascii 文本等);您可以处理输出,使其仅显示文件名,现在您希望将这些名称传递给 ls -l 命令以查看时间戳记。xargs 命令就是用来完成此项工作的。它允许您对输出执行其他某些命令。记住下面这个来自于第 1 部分中的语法: 
file -Lz * | grep ASCII | cut -d”:” -f1 | xargs ls -ltr
让我们来剖析这个命令字符串。第一个,file -Lz *,用于查找是符号链接或者经过压缩的文件。它将输出传递给下一个命令 grep ASCII,该命令在其中搜索 “ASCII” 字符串并产生如下所示的输出: alert_DBA102.log:        ASCII English text
alert_DBA102.log.Z:      ASCII text (compress’d data 16 bits)
dba102_asmb_12307.trc.Z: ASCII English text (compress’d data 16 bits)
dba102_asmb_20653.trc.Z: ASCII English text (compress’d data 16 bits)
由于我们只对文件名感兴趣,因此我们应用下一个命令 cut -d”:” -f1,仅显示第一个字段: alert_DBA102.log
alert_DBA102.log.Z
dba102_asmb_12307.trc.Z
dba102_asmb_20653.trc.Z
现在,我们希望使用 ls -l 命令,将上述列表作为参数进行传递,一次传递一个。xargs 命令允许您这样做。最后一部分,xargs ls -ltr,用于接收输出并对其执行 ls -ltr 命令,如下所示:
ls -ltr alert_DBA102.log
ls -ltr alert_DBA102.log.Z
ls -ltr dba102_asmb_12307.trc.Z
ls -ltr dba102_asmb_20653.trc.Z
因此,xargs 本身虽然没有多大用处,但在与其他命令相结合时,它的功能非常强大。
下面是另一个示例,我们希望计算这些文件中的行数:
$ file * | grep ASCII | cut -d”:” -f1  | xargs wc -l
  47853 alert_DBA102.log
     1Array dba102_cjq0_144Array3.trc
  2Array053 dba102_mmnl_144Array7.trc
    154 dba102_reco_144Array1.trc
     43 dba102_rvwr_14518.trc
  77122 total
(注:上述任务还可用以下命令完成:)
$ wc -l ‘file * | grep ASCII | cut -d”:” -f1 | grep ASCII | cut -d”:” -f1‘
该 xargs 版本用于阐释概念。Linux 可以用几种方法来完成同一个任务;请使用最适合您的情况的方法。
使用该方法,您可以快速重命名目录中的文件。
$ ls | xargs -t -i mv {} {}.bak
-i 选项告诉 xargs 用每项的名称替换 {}。-t 选项指示 xargs 先打印命令,然后再执行。
另一个非常有用的操作是当您使用 vi 打开要编辑的文件时:
$ file * | grep ASCII | cut -d”:” -f1 | xargs vi
该命令使用 vi 逐个打开文件。当您希望搜索多个文件并打开它们进行编辑时,使用该命令非常方便。 
它还有几个选项。最有用的可能是 -p 选项,它使操作具有可交互性:
$ file * | grep ASCII | cut -d”:” -f1 | xargs -p vi
vi alert_DBA102.log dba102_cjq0_144Array3.trc dba102_mmnl_144Array7.trc 
  dba102_reco_144Array1.trc dba102_rvwr_14518.trc ?…
此处的 xarg 要求您在运行每个命令之前进行确认。如果您按下 “y”,则执行命令。当您对文件进行某些可能有破坏且不可恢复的操作(如删除或覆盖)时,您会发现该选项非常有用。
-t 选项使用一个详细模式;它显示要运行的命令,是调试过程中一个非常有帮助的选项。
如果传递给 xargs 的输出为空怎么办?考虑以下命令:
$ file * | grep SSSSSS | cut -d”:” -f1 | xargs -t wc -l
wc -l 
            0
$
在此处,搜索 “SSSSSS” 后没有匹配的内容;因此 xargs 的输入均为空,如第二行所示(由于我们使用 -t 这个详细选项而产生的结果)。虽然这可能会有所帮助,但在某些情况下,如果没有要处理的内容,您可能希望停止 xargs;如果是这样,可以使用 -r 选项: $ file * | grep SSSSSS | cut -d”:” -f1 | xargs -t -r wc -l
$
如果没有要运行的内容,该命令退出。
假设您希望使用 rm 命令(该命令将作为 xargs 命令的参数)删除文件。然而,rm 只能接受有限数量的参数。如果您的参数列表超出该限制怎么办?xargs 的 -n 选项限制单个命令行的参数个数。 
下面显示了如何限制每个命令行仅使用两个参数:即使向 xargs ls -ltr 传递五个文件,但每次向 ls -ltr 仅传递两个文件。
$ file * | grep ASCII | cut -d”:” -f1 | xargs -t -n2 ls -ltr  
ls -ltr alert_DBA102.log dba102_cjq0_144Array3.trc 
-rw-r—–    1 oracle   dba           738 Aug 10 1Array:18 dba102_cjq0_144Array3.trc
-rw-r–r–    1 oracle   dba       2410225 Aug 13 05:31 alert_DBA102.log
ls -ltr dba102_mmnl_144Array7.trc dba102_reco_144Array1.trc 
-rw-r—–    1 oracle   dba       5386163 Aug 10 17:55 dba102_mmnl_144Array7.trc
-rw-r—–    1 oracle   dba          6808 Aug 13 05:21 dba102_reco_144Array1.trc
ls -ltr dba102_rvwr_14518.trc 
-rw-r—–    1 oracle   dba          2087 Aug 10 04:30 dba102_rvwr_14518.trc
使用该方法,您可以快速重命名目录中的文件。 
$ ls | xargs -t -i mv {} {}.bak
-i 选项告诉 xargs 用每项的名称替换 {}。

转自:http://www.sudu.cn/info/html/edu/linux/20080102/290238.html

一行shell脚本解决问题汇总(持续更新中)

 linux  一行shell脚本解决问题汇总(持续更新中)已关闭评论
1月 072014
 

问题: 哪个进程打开了的文件句柄最多?

lsof -n |awk ‘{print $2}’|sort|uniq -c |sort -nr|more

问题: 如何查看tcp连接汇总统计信息?
sudo netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’

问题: 如何杀死所有应用相关进程?(以下以java进程举例)

ps -ef | grep java | grep -v grep | awk ‘{print $2}’ | xargs sudo kill -9

问题: LINUX查看进程开始时间、结束时间、运行时间?
ps -eo pid,tty,user,comm,stime,etime | more 

问题: 如何将文字进行转码?

使用iconv,如下面将中国有UTF-8转为GB2312

echo “中国” | iconv f UTF8 t GB2312


问题:redis中如何批量删除key?

redis-cli -n 0 KEYS “topic*” | xargs redis-cli -n 0 DEL

 注: redis-cli应该替换为redis完整路径 ; -n后面的0表示0切片。

问题:查看所有用户的crontab?

cut -d: -f1 /etc/passwd|xargs -i sudo crontab -u {} -l

问题:查找最新的一个目录/文件?

ls -t *.cpp | head -1    //查找当前目录下的.cpp结尾文件,从新到旧排列,取第一个

ls -t *.cpp | tail -1    //查找当前目录下的.cpp结尾文件,从排列,取最后一个

问题: 如何获取特征行间的内容

比如:文件aaa中:
.......
base x 
10
23
34
base y
ttt
zz
uu
base x
9443
1043
1132
base y
......
现在需要提取base x 和base y行中的内容,如何使用命令呢?


使用下面命令: awk 'BEGIN{FS="base x";RS="base y"} {printf("%s", $2)}' aaa

解释:

RS:Record Separator,记录分隔符

FS:Field Separator,字段分隔符

问题: 如何获取某字符串MD5/BASE64值
MD5:     echo -n "test" | md5sum                // echo -n  参数表示不打印换行符
BASE64:  echo -n "test测试" | base64


问题:查询最大的10个文件或文件夹

du -a /var | sort -n -r | head -n 10    // var为查询目录

shell脚本中一些特殊符号(` $ ! # ; |)

 linux  shell脚本中一些特殊符号(` $ ! # ; |)已关闭评论
10月 122013
 
在shell中常用的特殊符号罗列如下: 



# ;   ;; . , / 'string'| !   $   ${}   $? $$   $* 



"string"* **   ? : ^ $#   $@ `command`{}   [] [[]] ()   (()) 



||   && {xx,yy,zz,…}~   ~+   ~-   &   <…>   + – %=   ==   != 



   

# 井号 (comments) 

这几乎是个满场都有的符号,除了先前已经提过的"第一行" 

#!/bin/bash 

井号也常出现在一行的开头,或者位于完整指令之后,这类情况表示符号后面的是注解文字,不会被执行。 

# This line is comments. 

echo "a = $a" # a = 0 

由于这个特性,当临时不想执行某行指令时,只需在该行开头加上 # 就行了。这常用在撰写过程中。 

#echo "a = $a" # a = 0 

如果被用在指令中,或者引号双引号括住的话,或者在倒斜线的后面,那他就变成一般符号,不具上述的特殊功能。 





~ 帐户的 home 目录 

算是个常见的符号,代表使用者的 home 目录:cd ~;也可以直接在符号后加上某帐户的名称:cd ~user或者当成是路径的一部份:~/bin 

~+ 当前的工作目录,这个符号代表当前的工作目录,她和内建指令 pwd的作用是相同的。 

# echo ~+/var/log 

~- 上次的工作目录,这个符号代表上次的工作目录。 

# echo ~-/etc/httpd/logs 





; 分号 (Command separator) 

在 shell 中,担任"连续指令"功能的符号就是"分号"。譬如以下的例子:cd ~/backup ; mkdir startup ;cp ~/.* startup/. 





;; 连续分号 (Terminator) 

专用在 case 的选项,担任 Terminator 的角色。 

case "$fop" inhelp) echo "Usage: Command -help -version filename";;version) echo "version 0.1" ;;esac 





. 逗号 (dot,就是“点”) 

在 shell 中,使用者应该都清楚,一个 dot 代表当前目录,两个 dot 代表上层目录。 

CDPATH=.:~:/home:/home/web:/var:/usr/local 

在上行 CDPATH 的设定中,等号后的 dot 代表的就是当前目录的意思。 

如果档案名称以 dot 开头,该档案就属特殊档案,用 ls 指令必须加上 -a 选项才会显示。除此之外,在 regularexpression 中,一个 dot 代表匹配一个字元。 





'string' 单引号 (single quote) 

被单引号用括住的内容,将被视为单一字串。在引号内的代表变数的$符号,没有作用,也就是说,他被视为一般符号处理,防止任何变量替换。 

heyyou=homeecho '$heyyou' # We get $heyyou 





"string" 双引号 (double quote) 

被双引号用括住的内容,将被视为单一字串。它防止通配符扩展,但允许变量扩展。这点与单引数的处理方式不同。 

heyyou=homeecho "$heyyou" # We get home 



`command` 倒引号 (backticks) 

在前面的单双引号,括住的是字串,但如果该字串是一列命令列,会怎样?答案是不会执行。要处理这种情况,我们得用倒单引号来做。 

fdv=`date +%F`echo "Today $fdv" 

在倒引号内的 date +%F 会被视为指令,执行的结果会带入 fdv 变数中。 





, 逗点 (comma,标点中的逗号) 

这个符号常运用在运算当中当做"区隔"用途。如下例 

#!/bin/bashlet "t1 = ((a = 5 + 3, b = 7 – 1, c = 15 / 3))"echo "t1= $t1, a = $a, b = $b" 





/ 斜线 (forward slash) 

在路径表示时,代表目录。 

cd /etc/rc.dcd ../..cd / 

通常单一的 / 代表 root 根目录的意思;在四则运算中,代表除法的符号。 

let "num1 = ((a = 10 / 2, b = 25 / 5))" 





倒斜线 

在交互模式下的escape 字元,有几个作用;放在指令前,有取消 aliases的作用;放在特殊符号前,则该特殊符号的作用消失;放在指令的最末端,表示指令连接下一行。 

# type rmrm is aliased to `rm -i'# rm ./*.log 

上例,我在 rm 指令前加上 escape 字元,作用是暂时取消别名的功能,将 rm 指令还原。 

# bkdir=/home# echo "Backup dir, $bkdir = $bkdir"Backup dir,$bkdir = /home 

上例 echo 内的 $bkdir,escape 将 $ 变数的功能取消了,因此,会输出 $bkdir,而第二个 $bkdir则会输出变数的内容 /home。 





| 管道 (pipeline) 

pipeline 是 UNIX 系统,基础且重要的观念。连结上个指令的标准输出,做为下个指令的标准输入。 

who | wc -l 

善用这个观念,对精简 script 有相当的帮助。 





! 惊叹号(negate or reverse) 

通常它代表反逻辑的作用,譬如条件侦测中,用 != 来代表"不等于" 

if [ “$?” != 0 ]thenecho "Executes error"exit 1fi 

在规则表达式中她担任 "反逻辑" 的角色 

ls a[!0-9] 

上例,代表显示除了a0, a1 …. a9 这几个文件的其他文件。 





: 冒号 

在 bash 中,这是一个内建指令:"什么事都不干",但返回状态值 0。 



echo $? # 回应为 0 

: > f.$$ 

上面这一行,相当于 cat /dev/null >f.$$。不仅写法简短了,而且执行效率也好上许多。 

有时,也会出现以下这类的用法 

: ${HOSTNAME?} ${USER?} ${MAIL?} 

这行的作用是,检查这些环境变数是否已设置,没有设置的将会以标准错误显示错误讯息。像这种检查如果使用类似 test 或 if这类的做法,基本上也可以处理,但都比不上上例的简洁与效率。 





? 问号 (wild card) 

在文件名扩展(Filename expansion)上扮演的角色是匹配一个任意的字元,但不包含 null 字元。 

# ls a?a1 

善用她的特点,可以做比较精确的档名匹配。 





* 星号 (wild card) 

相当常用的符号。在文件名扩展(Filename expansion)上,她用来代表任何字元,包含 null 字元。 

# ls a*a a1 access_log 

在运算时,它则代表 "乘法"。 

let "fmult=2*3" 

除了内建指令 let,还有一个关于运算的指令expr,星号在这里也担任"乘法"的角色。不过在使用上得小心,他的前面必须加上escape 字元。 





** 次方运算 

两个星号在运算时代表 "次方" 的意思。 

let "sus=2**3"echo "sus = $sus" # sus = 8 





$ 钱号(dollar sign) 

变量替换(Variable Substitution)的代表符号。 

vrs=123echo "vrs = $vrs" # vrs = 123 

另外,在 Regular Expressions 里被定义为 "行" 的最末端 (end-of-line)。这个常用在grep、sed、awk 以及 vim(vi) 当中。 





${} 变量的正规表达式 

bash 对 ${} 定义了不少用法。以下是取自线上说明的表列 

   ${parameter:-word}   ${parameter:=word}   ${parameter:?word}   ${parameter:+word}   ${parameterffset}   ${parameterffset:length}   ${!prefix*}   ${#parameter}   ${parameter#word}   ${parameter##word}   ${parameter%word}   ${parameter%%word}   ${parameter/pattern/string}   ${parameter//pattern/string} 





$* 
$* 引用script的执行引用变量,引用参数的算法与一般指令相同,指令本身为0,其后为1,然后依此类推。引用变量的代表方式如下: 

$0, $1, $2, $3, $4, $5, $6, $7, $8, $9, ${10}, ${11}….. 

个位数的,可直接使用数字,但两位数以上,则必须使用 {} 符号来括住。 

$* 则是代表所有引用变量的符号。使用时,得视情况加上双引号。 

echo "$*" 

还有一个与 $* 具有相同作用的符号,但效用与处理方式略为不同的符号。 





$@ 

$@ 与 $* 具有相同作用的符号,不过她们两者有一个不同点。 

符号 $* 将所有的引用变量视为一个整体。但符号 $@ 则仍旧保留每个引用变量的区段观念。 



$# 

这也是与引用变量相关的符号,她的作用是告诉你,引用变量的总数量是多少。 

echo "$#" 





$? 状态值 (status variable) 

一般来说,UNIX(linux) 系统的进程以执行系统调用exit()来结束的。这个回传值就是status值。回传给父进程,用来检查子进程的执行状态。 

一般指令程序倘若执行成功,其回传值为 0;失败为 1。 

tar cvfz dfbackup.tar.gz /home/user > /dev/nullecho"$?"$$ 

由于进程的ID是唯一的,所以在同一个时间,不可能有重复性的 PID。有时,script会需要产生临时文件,用来存放必要的资料。而此script亦有可能在同一时间被使用者们使用。在这种情况下,固定文件名在写法上就显的不可靠。唯有产生动态文件名,才能符合需要。符号$$或许可以符合这种需求。它代表当前shell 的 PID。 

echo "$HOSTNAME, $USER, $MAIL" > ftmp.$$ 

使用它来作为文件名的一部份,可以避免在同一时间,产生相同文件名的覆盖现象。 

ps: 基本上,系统会回收执行完毕的 PID,然后再次依需要分配使用。所以 script 即使临时文件是使用动态档名的写法,如果script 执行完毕后仍不加以清除,会产生其他问题。 



(   ) 指令群组 (command group) 

用括号将一串连续指令括起来,这种用法对 shell 来说,称为指令群组。如下面的例子:(cd ~ ; vcgh=`pwd` ;echo $vcgh),指令群组有一个特性,shell会以产生 subshell来执行这组指令。因此,在其中所定义的变数,仅作用于指令群组本身。我们来看个例子 

# cat ftmp-01#!/bin/basha=fsh(a=incg ; echo -e "n $a n")echo $a#./ftmp-01incgfsh 

除了上述的指令群组,括号也用在 array 变数的定义上;另外也应用在其他可能需要加上escape字元才能使用的场合,如运算式。 





(( )) 

这组符号的作用与 let 指令相似,用在算数运算上,是 bash 的内建功能。所以,在执行效率上会比使用 let指令要好许多。 

#!/bin/bash(( a = 10 ))echo -e "inital value, a = $an"(( a++))echo "after a++, a = $a" 



{ } 大括号 (Block of code) 

有时候 script 当中会出现,大括号中会夹着一段或几段以"分号"做结尾的指令或变数设定。 

# cat ftmp-02#!/bin/basha=fsh{a=inbc ; echo -e "n $a n"}echo $a#./ftmp-02inbcinbc 

这种用法与上面介绍的指令群组非常相似,但有个不同点,它在当前的 shell 执行,不会产生 subshell。 

大括号也被运用在 "函数" 的功能上。广义地说,单纯只使用大括号时,作用就像是个没有指定名称的函数一般。因此,这样写 script也是相当好的一件事。尤其对输出输入的重导向上,这个做法可精简 script 的复杂度。 



此外,大括号还有另一种用法,如下 

{xx,yy,zz,…} 

这种大括号的组合,常用在字串的组合上,来看个例子 

mkdir {userA,userB,userC}-{home,bin,data} 

我们得到 userA-home, userA-bin, userA-data, userB-home, userB-bin,userB-data, userC-home, userC-bin,userC-data,这几个目录。这组符号在适用性上相当广泛。能加以善用的话,回报是精简与效率。像下面的例子 

chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}} 

如果不是因为支援这种用法,我们得写几行重复几次呀! 





[   ] 中括号 

常出现在流程控制中,扮演括住判断式的作用。if [ “$?” != 0 ]thenecho "Executes error"exit1fi 

这个符号在正则表达式中担任类似 "范围" 或 "集合" 的角色 

rm -r 200[1234] 

上例,代表删除 2001, 2002, 2003, 2004 等目录的意思。 





[[ ]] 

这组符号与先前的 [] 符号,基本上作用相同,但她允许在其中直接使用 || 与&& 逻辑等符号。 

#!/bin/bashread akif [[ $ak > 5 || $ak< 9 ]]thenecho $akfi 





|| 逻辑符号 

这个会时常看到,代表 or 逻辑的符号。 





&& 逻辑符号 

这个也会常看到,代表 and 逻辑的符号。 





& 后台工作 

单一个& 符号,且放在完整指令列的最后端,即表示将该指令列放入后台中工作。 

tar cvfz data.tar.gz data > /dev/null& 



<…> 单字边界 

这组符号在规则表达式中,被定义为"边界"的意思。譬如,当我们想找寻 the 这个单字时,如果我们用 

grep the FileA 

你将会发现,像 there 这类的单字,也会被当成是匹配的单字。因为 the 正巧是 there的一部份。如果我们要必免这种情况,就得加上 "边界" 的符号 

grep '' FileA 





+ 加号 (plus) 

在运算式中,她用来表示 "加法"。 

expr 1 + 2 + 3 

此外在规则表达式中,用来表示"很多个"的前面字元的意思。 

# grep '10+9' fileB109100910000910000931010009#这个符号在使用时,前面必须加上escape 字元。 





– 减号 (dash) 

在运算式中,她用来表示 "减法"。 

expr 10 – 2 

此外也是系统指令的选项符号。 

ls -expr 10 – 2 

在 GNU 指令中,如果单独使用 – 符号,不加任何该加的文件名称时,代表"标准输入"的意思。这是 GNU指令的共通选项。譬如下例 

tar xpvf – 

这里的 – 符号,既代表从标准输入读取资料。 

不过,在 cd 指令中则比较特别 

cd – 

这代表变更工作目录到"上一次"工作目录。 





% 除法 (Modulo) 

在运算式中,用来表示 "除法"。 

expr 10 % 2 

此外,也被运用在关于变量的规则表达式当中的下列 

${parameter%word}${parameter%%word} 

一个 % 表示最短的 word 匹配,两个表示最长的 word 匹配。 





= 等号 (Equals) 

常在设定变数时看到的符号。 

vara=123echo " vara = $vara" 

或者像是 PATH 的设定,甚至应用在运算或判断式等此类用途上。 





== 等号 (Equals) 

常在条件判断式中看到,代表 "等于" 的意思。 

if [ $vara == $varb ] 

…下略 



!= 不等于 

常在条件判断式中看到,代表 "不等于" 的意思。 

if [ $vara != $varb ] 

…下略 





^ 

这个符号在规则表达式中,代表行的 "开头" 位置,在[]中也与"!"(叹号)一样表示“非” 





输出/输入重导向 

> >>   <   <<   :>   &>   2&>   2<>>&   >&2  



文件描述符(File Descriptor),用一个数字(通常为0-9)来表示一个文件。 

常用的文件描述符如下: 

文件描述符     名称     常用缩写 默认值 

0        标准输入 stdin    键盘 

1        标准输出 stdout     屏幕 

2    标准错误输出   stderr     屏幕 

我们在简单地用<或>时,相当于使用 0< 或 1>(下面会详细介绍)。 

* cmd > file 

把cmd命令的输出重定向到文件file中。如果file已经存在,则清空原有文件,使用bash的noclobber选项可以防止复盖原有文件。 

* cmd >> file 

把cmd命令的输出重定向到文件file中,如果file已经存在,则把信息加在原有文件後面。 

* cmd < file 

使cmd命令从file读入 

* cmd << text 

从命令行读取输入,直到一个与text相同的行结束。除非使用引号把输入括起来,此模式将对输入内容进行shell变量替换。如果使用<<- ,则会忽略接下来输入行首的tab,结束行也可以是一堆tab再加上一个与text相同的内容,可以参考後面的例子。 

* cmd <<< word 

把word(而不是文件word)和後面的换行作为输入提供给cmd。 

* cmd <> file 

以读写模式把文件file重定向到输入,文件file不会被破坏。仅当应用程序利用了这一特性时,它才是有意义的。 

* cmd >| file 

功能同>,但即便在设置了noclobber时也会复盖file文件,注意用的是|而非一些书中说的!,目前仅在csh中仍沿用>!实现这一功能。 

: > filename    把文件"filename"截断为0长度.# 如果文件不存在, 那么就创建一个0长度的文件(与'touch'的效果相同). 

cmd >&n 把输出送到文件描述符n 

cmd m>&n 把输出 到文件符m的信息重定向到文件描述符n 

cmd >&- 关闭标准输出 

cmd <&n 输入来自文件描述符n 

cmd m<&n m来自文件描述各个n 

cmd <&- 关闭标准输入 

cmd <&n- 移动输入文件描述符n而非复制它。(需要解释) 

cmd >&n- 移动输出文件描述符 n而非复制它。(需要解释) 

注意: >&实际上复制了文件描述符,这使得cmd > file 2>&1与cmd 2>&1 >file的效果不一样。
 

 

 http://hi.baidu.com/hellosimple/blog/item/559483f1f1c0e95e352acc89.html

 

 http://blog.csdn.net/mqboss/article/details/6549176

1、{} 大括号:

用法一:通配符扩展 

代码: 
ls my_{finger,toe}s



这条命令相当于如下命令的组合:

代码: 
ls my_fingers my_toes

mkdir {userA,userB,userC}-{home,bin,data}



我们将得到 userA-home, userA-bin, userA-data, userB-home, userB-bin,userB-data,userC-home, userC-bin, userC-data,这几个目录

用法二:可用于语句块的构造,语句之间用回车隔开。如果你想在某些使用单个语句的地方(比如在AND或OR列表中)使用多条语句,你可以把它们括在花括号{}中来构造一个语句块。

代码: 
grep -v "$cdcatnum" $strack_file > $temp_file

cat $temp_file > $strack_file

echo

cat -n file1



(注:以上大括号中的四句命令够成了一个语句块)

用法三:参数扩展

代码: 
${name:-default} 使用一个默认值(一般是空值)来代替那些空的或者没有赋值的变量name;

${name:=default}使用指定值来代替空的或者没有赋值的变量name;

${name:?message}如果变量为空或者未赋值,那么就会显示出错误信息并中止脚本的执行同时返回退出码1。

${#name} 给出name的长度

${name%word} 从name的尾部开始删除与word匹配的最小部分,然后返回剩余部分

${name%%word} 从name的尾部开始删除与word匹配的最长部分,然后返回剩余部分

${name#word} 从name的头部开始删除与word匹配的最小部分,然后返回剩余部分

${name##word} 从name的头部开始删除与word匹配的最长部分,然后返回剩余部分



(注,name为变量名,word为要匹配的字符串)

用法三在处理字符串和未知变量时,是很有用的。



2、[] 中括号: 

用法一:通配符扩展:

允许匹配方括号中任何一个单个字符

代码: 
ls /[eh][to][cm]*



相当于执行 ls /etc /home(若有/eom目录,就相当于会执行ls /etc /home /eom)

注:在mkdir命令下不能扩展

用法二:用于条件判断符号:

[]符号可理解为指向test命令的一个软链接,所以其用法可完全参照test,将test位置替换为[便可。

代码: 
if [ "$?" != 0 ] 等价于 if test "$?" != 0

then echo "Executes error"





3、`command` 反引号:

`command`与$(command)的含义相同,都是返回当前执行命令的结果 

代码: 
#!/bin/sh

for file in $(ls f*.sh);do

    lpr $file

done

exit 0



该例实现了扩展f*.sh给出所有匹配模式的文件的名字。



4、'string' 单引号 和 "string" 双引号 

双引号:如果想在定义的变量中加入空格,就必须使用单引号或双引号,

单、双引号的区别在于双引号转义特殊字符而单引号不转义特殊字符

代码: 
$ heyyou=home

$ echo '$heyyou'

$ $heyyou ($没有转义)

eg: $ heyyou=home

$ echo "$heyyou"

$ home (很明显,$转义了输出了heyyou变量的值)





5、$# 它的作用是告诉你引用变量的总数量是多少; 

代码: 
$$ 它的作用是告诉你shell脚本的进程号;

$* 以一个单字符串显示所有的脚本传递的参数。等价于$1 $2 $3.......;

$@ 与$*基本类似(参见序号7),但在数组赋值时有些不同;

$? 前一个命令的退出码;

$- 显示shell使用的当前选项;

$! 最后一个后台运行的进程ID号。





6、$((…))语法:对括号内的表达式求值 

代码: 
#!/bin/sh

x=0

hile [ "$x" -ne 10 ];do

echo $x

x=$(($x+1))

done

exit 0





7、shell中几种特殊的参数变量的引用 

代码: 
$1、$2、$3……${10}、${11}、${12}…… :表示脚本传入的的各个参数,注意当需表示两位数以后的参数时数字要用花括号括起。

$@ 列出所有的参数,各参数用空格隔开

$*: 列出所有的参数,各参数用环境变量IFS的第一个字符隔开





8、命令列表: 

AND列表 statement1 && statement2 && statement3 && …:只有在前面所有的命令都执行成功的情况下才执行后一条命令

OR列表 statement1 || statement2 || statement3 || …:允许执行一系列命令直到有一条命令成功为止,其后所有命令将不再被执行

#!/bin/sh

代码: 
touch file_one

rm -f file_two

if [ -f file_one ] && echo "hello" && [ -f file_two ] && echo " there"

then

echo "in if"

else

echo "in else"

fi

exit 0



上例的输出为:

代码: 
hello

in else



关于AND列表与OR列表,在逻辑判断中很使用,下面就举一个其最常用的例子:

代码: 
[ condition ] && command for true || command for false:



当条件为真时,执行commandfor true ,当条件为假时,执行command for false



9、: 冒号:内建空指令,返回值为0 

代码: 
$ :

$ echo $?

$ 0

while: (该语句结构可实现一个无限循环)





10、; 分号: 在 shell 中,担任"连续指令"功能的符号就是"分号" 

代码: 
cd ~/backup ; mkdir startup ; cp ~/.* startup/.





11、# 井号:表示符号后面的是注解文字,不会被执行; 

代码: 
* 匹配文件名中的任何字符,包括字符串;

? 匹配文件名中的任何单个字符。

~ 代表使用者的 home 目录





12、/ 倒斜线:

放在指令前,有取消 aliases(别名) 的作用;

放在特殊符号前,则该特殊符号的作用消失;

放在指令的最末端,表示指令连接下一行(使得回车符无效,只起换行作用)



13、! 感叹号:

通常它代表反逻辑的作用,譬如条件侦测中,用 != 来代表"不等于" 



14、** 次方运算:两个星号在运算时代表 "次方" 的意思 

代码: 
let "sus=2**3"

echo "sus = $sus"

$ sus = 8
转自:http://www.cnblogs.com/xuxm2007/archive/2011/10/20/2218846.html

Bash 实例,第 2 部分(转)

 linux  Bash 实例,第 2 部分(转)已关闭评论
3月 072013
 

转自:http://www.ibm.com/developerworks/cn/linux/shell/bash/bash-2/index.html

我们先看一下处理命令行自变量的简单技巧,然后再看看 bash 基本编程结构。

接收自变量

在 介绍性文章 中的样本程序中,我们使用环境变量 "$1" 来引用第一个命令行自变量。类似地,可以使用 "$2"、"$3" 等来引用传递给脚本的第二和第三个自变量。这里有一个例子:

 #!/usr/bin/env bash

 echo name of script is $0
 echo first argument is $1
 echo second argument is $2
 echo seventeenth argument is $17
 echo number of arguments is $#

 

除以下两个细节之外,此例无需说明。第一,"$0" 将扩展成从命令行调用的脚本名称,"$#" 将扩展成传递给脚本的自变量数目。试验以上脚本,通过传递不同类型的命令行自变量来了解其工作原理。

有时需要一次引用 所有 命令行自变量。针对这种用途,bash 实现了变量 "$@",它扩展成所有用空格分开的命令行参数。在本文稍后的 "for" 循环部分中,您将看到使用该变量的例子。

 

Bash 编程结构

如果您曾用过如 C、Pascal、Python 或 Perl 那样的过程语言编程,则一定熟悉 "if" 语句和 "for" 循环那样的标准编程结构。对于这些标准结构的大多数,Bash 有自己的版本。在下几节中,将介绍几种 bash 结构,并演示这些结构和您已经熟悉的其它编程语言中结构的差异。如果以前编程不多,也不必担心。我提供了足够的信息和示例,使您可以跟上本文的进度。

 

方便的条件语句

如果您曾用 C 编写过与文件相关的代码,则应该知道:要比较特定文件是否比另一个文件新需要大量工作。那是因为 C 没有任何内置语法来进行这种比较,必须使用两个 stat() 调用和两个 stat 结构来进行手工比较。相反,bash 内置了标准文件比较运算符,因此,确定“/tmp/myfile 是否可读”与查看“$myvar 是否大于 4”一样容易。

下表列出最常用的 bash 比较运算符。同时还有如何正确使用每一选项的示例。示例要跟在 "if" 之后。例如:

 if [ -z "$myvar" ]
then
    echo "myvar is not defined"
fi

 

运算符 描述 示例
文件比较运算符
-e filename 如果 filename存在,则为真 [ -e /var/log/syslog ]
-d filename 如果 filename为目录,则为真 [ -d /tmp/mydir ]
-f filename 如果 filename为常规文件,则为真 [ -f /usr/bin/grep ]
-L filename 如果 filename为符号链接,则为真 [ -L /usr/bin/grep ]
-r filename 如果 filename可读,则为真 [ -r /var/log/syslog ]
-w filename 如果 filename可写,则为真 [ -w /var/mytmp.txt ]
-x filename 如果 filename可执行,则为真 [ -L /usr/bin/grep ]
filename1-nt filename2 如果 filename1比 filename2新,则为真 [ /tmp/install/etc/services -nt /etc/services ]
filename1-ot filename2 如果 filename1比 filename2旧,则为真 [ /boot/bzImage -ot arch/i386/boot/bzImage ]
字符串比较运算符 (请注意引号的使用,这是防止空格扰乱代码的好方法)
-z string 如果 string长度为零,则为真 [ -z “$myvar” ]
-n string 如果 string长度非零,则为真 [ -n “$myvar” ]
string1string2 如果 string1与 string2相同,则为真 [ “$myvar” = “one two three” ]
string1!= string2 如果 string1与 string2不同,则为真 [ “$myvar” != “one two three” ]
算术比较运算符
num1-eq num2 等于 [ 3 -eq $mynum ]
num1-ne num2 不等于 [ 3 -ne $mynum ]
num1-lt num2 小于 [ 3 -lt $mynum ]
num1-le num2 小于或等于 [ 3 -le $mynum ]
num1-gt num2 大于 [ 3 -gt $mynum ]
num1-ge num2 大于或等于 [ 3 -ge $mynum ]

有时,有几种不同方法来进行特定比较。例如,以下两个代码段的功能相同:

 if [ "$myvar" -eq 3 ]
then
     echo "myvar equals 3"
fi


 if [ "$myvar" = "3" ]
then
     echo "myvar equals 3"
fi

 

上面两个比较执行相同的功能,但是第一个使用算术比较运算符,而第二个使用字符串比较运算符。

 

字符串比较说明

大多数时候,虽然可以不使用括起字符串和字符串变量的双引号,但这并不是好主意。为什么呢?因为如果环境变量中恰巧有一个空格或制表键,bash 将无法分辨,从而无法正常工作。这里有一个错误的比较示例:

 if [ $myvar = "foo bar oni" ]
then
     echo "yes"
fi

 

在上例中,如果 myvar 等于 "foo",则代码将按预想工作,不进行打印。但是,如果 myvar 等于 "foo bar oni",则代码将因以下错误失败:

 [: too many arguments

 

在这种情况下,"$myvar"(等于 "foo bar oni")中的空格迷惑了 bash。bash 扩展 "$myvar" 之后,代码如下:

 [ foo bar oni = "foo bar oni" ]

 

因为环境变量没放在双引号中,所以 bash 认为方括号中的自变量过多。可以用双引号将字符串自变量括起来消除该问题。请记住,如果养成将所有字符串自变量用双引号括起的习惯,将除去很多类似的编程错误。"foo bar oni" 比较 应该写成:

 if [ "$myvar" = "foo bar oni" ]
then
     echo "yes"
fi

 

更多引用细节

如果要扩展环境变量,则必须将它们用 双引号、而不是单引号括起。单引号 禁用 变量(和历史)扩展。

以上代码将按预想工作,而不会有任何令人不快的意外出现。

 

循环结构:"for"

好了,已经讲了条件语句,下面该探索 bash 循环结构了。我们将从标准的 "for" 循环开始。这里有一个简单的例子:


 #!/usr/bin/env bash

 for x in one two three four
 do
     echo number $x
 done


        输出:

 number one
 number two
 number three
 number four
      

 

发生了什么?"for" 循环中的 "for x" 部分定义了一个名为 "$x" 的新环境变量(也称为循环控制变量),它的值被依次设置为 "one"、"two"、"three" 和 "four"。每一次赋值之后,执行一次循环体("do" 和 "done" 之间的代码)。在循环体内,象其它环境变量一样,使用标准的变量扩展语法来引用循环控制变量 "$x"。还要注意,"for" 循环总是接收 "in" 语句之后的某种类型的字列表。在本例中,指定了四个英语单词,但是字列表也可以引用磁盘上的文件,甚至文件通配符。看看下面的例子,该例演示如何使用标准 shell 通配符:

 #!/usr/bin/env bash

 for myfile in /etc/r*
 do
     if [ -d "$myfile" ]
then
       echo "$myfile (dir)"
else
       echo "$myfile"
fi
 done


        输出:

 /etc/rc.d (dir)
 /etc/resolv.conf
 /etc/resolv.conf~
 /etc/rpc
      

 

以上代码列出在 /etc 中每个以 "r" 开头的文件。要做到这点,bash 在执行循环之前首先取得通配符 /etc/r*,然后扩展它,用字符串 /etc/rc.d /etc/resolv.conf /etc/resolv.conf~ /etc/rpc 替换。一旦进入循环,根据 myfile 是否为目录,"-d" 条件运算符用来执行两个不同操作。如果是目录,则将 "(dir)" 附加到输出行。

还可以在字列表中使用多个通配符、甚至是环境变量:


 for x in /etc/r--? /var/lo* /home/drobbins/mystuff/* /tmp/${MYPATH}/*
 do
     cp $x /mnt/mydir
 done

 

Bash 将在所有正确位置上执行通配符和环境变量扩展,并可能创建一个非常长的字列表。

虽然所有通配符扩展示例使用了 绝对路径,但也可以使用相对路径,如下所示:


 for x in ../* mystuff/*
 do
     echo $x is a silly file
 done

 

在上例中,bash 相对于当前工作目录执行通配符扩展,就象在命令行中使用相对路径一样。研究一下通配符扩展。您将注意到,如果在通配符中使用绝对路径,bash 将通配符扩展成一个绝对路径列表。否则,bash 将在后面的字列表中使用相对路径。如果只引用当前工作目录中的文件(例如,如果输入 "for x in *"),则产生的文件列表将没有路径信息的前缀。请记住,可以使用 "basename" 可执行程序来除去前面的路径信息,如下所示:


 for x in /var/log/*
 do
     echo `basename $x` is a file living in /var/log
 done

 

当然,在脚本的命令行自变量上执行循环通常很方便。这里有一个如何使用本文开始提到的 "$@" 变量的例子:


 #!/usr/bin/env bash

 for thing in "$@"
 do
     echo you typed ${thing}.
 done


        输出:

 $ allargs hello there you silly
 you typed hello.
 you typed there.
 you typed you.
 you typed silly.
      

 

 

Shell 算术

在学习另一类型的循环结构之前,最好先熟悉如何执行 shell 算术。是的,确实如此:可以使用 shell 结构来执行简单的整数运算。只需将特定的算术表达式用 "$((" 和 "))" 括起,bash 就可以计算表达式。这里有一些例子:

 $ echo $(( 100 / 3 ))
 33
 $ myvar="56"
 $ echo $(( $myvar + 12 ))
 68
 $ echo $(( $myvar - $myvar ))
0 $ myvar=$(( $myvar + 1 ))
$ echo $myvar
 57

 

 

更多的循环结构:"while" 和 "until"

只要特定条件为真,"while" 语句就会执行,其格式如下:

 while [ condition ]
 do
     statements
 done

 

通常使用 "While" 语句来循环一定次数,比如,下例将循环 10 次:

 myvar=0
 while [ $myvar -ne 10 ]
 do
     echo $myvar
     myvar=$(( $myvar + 1 ))
 done

 

可以看到,上例使用了算术表达式来使条件最终为假,并导致循环终止。

"Until" 语句提供了与 "while" 语句相反的功能:只要特定条件为  ,它们就重复。下面是一个与前面的 "while" 循环具有同等功能的 "until" 循环:

 myvar=0
 until [ $myvar -eq 10 ]
 do
     echo $myvar
     myvar=$(( $myvar + 1 ))
 done

 

 

Case 语句

Case 语句是另一种便利的条件结构。这里有一个示例片段:


 case "${x##*.}" in
      gz)
            gzunpack ${SROOT}/${x}
            ;;
      bz2)
            bz2unpack ${SROOT}/${x}
            ;;
      *)
            echo "Archive format not recognized."
            exit
            ;;
 esac

 

在上例中,bash 首先扩展 "${x##*.}"。在代码中,"$x" 是文件的名称,"${x##.*}" 除去文件中最后句点后文本之外的所有文本。然后,bash 将产生的字符串与 ")" 左边列出的值做比较。在本例中,"${x##.*}" 先与 "gz" 比较,然后是 "bz2",最后是 "*"。如果 "${x##.*}" 与这些字符串或模式中的任何一个匹配,则执行紧接 ")" 之后的行,直到 ";;" 为止,然后 bash 继续执行结束符 "esac" 之后的行。如果不匹配任何模式或字符串,则不执行任何代码行,在这个特殊的代码片段中,至少要执行一个代码块,因为任何不与 "gz" 或 "bz2" 匹配的字符串都将与 "*" 模式匹配。

 

函数与名称空间

在 bash 中,甚至可以定义与其它过程语言(如 Pascal 和 C)类似的函数。在 bash 中,函数甚至可以使用与脚本接收命令行自变量类似的方式来接收自变量。让我们看一下样本函数定义,然后再从那里继续:


 tarview() {
     echo -n "Displaying contents of $1 "
     if [ ${1##*.} = tar ]
then
         echo "(uncompressed tar)"
         tar tvf $1
     elif [ ${1##*.} = gz ]
then
         echo "(gzip-compressed tar)"
         tar tzvf $1
     elif [ ${1##*.} = bz2 ]
then
         echo "(bzip2-compressed tar)"
         cat $1 | bzip2 -d | tar tvf -
fi
 }

 

另一种情况

可以使用 "case" 语句来编写上面的代码。您知道如何编写吗?

我们在上面定义了一个名为 "tarview" 的函数,它接收一个自变量,即某种类型的 tar 文件。在执行该函数时,它确定自变量是哪种 tar 文件类型(未压缩的、gzip 压缩的或 bzip2 压缩的),打印一行信息性消息,然后显示 tar 文件的内容。应该如下调用上面的函数(在输入、粘贴或找到该函数后,从脚本或命令行调用它):

 $ tarview shorten.tar.gz
 Displaying contents of shorten.tar.gz (gzip-compressed tar)
 drwxr-xr-x ajr/abbot         0 1999-02-27 16:17 shorten-2.3a/
 -rw-r--r-- ajr/abbot      1143 1997-09-04 04:06 shorten-2.3a/Makefile
 -rw-r--r-- ajr/abbot      1199 1996-02-04 12:24 shorten-2.3a/INSTALL
 -rw-r--r-- ajr/abbot       839 1996-05-29 00:19 shorten-2.3a/LICENSE
 ....

 

交互地使用它们

别忘了,可以将函数(如上面的函数)放在 ~/.bashrc 或 ~/.bash_profile 中,以便在 bash 中随时使用它们。

如您所见,可以使用与引用命令行自变量同样的机制来在函数定义内部引用自变量。另外,将把 "$#" 宏扩展成包含自变量的数目。唯一可能不完全相同的是变量 "$0",它将扩展成字符串 "bash"(如果从 shell 交互运行函数)或调用函数的脚本名称。

 

名称空间

经常需要在函数中创建环境变量。虽然有可能,但是还有一个技术细节应该了解。在大多数编译语言(如 C)中,当在函数内部创建变量时,变量被放置在单独的局部名称空间中。因此,如果在 C 中定义一个名为 myfunction 的函数,并在该函数中定义一个名为 "x" 的自变量,则任何名为 "x" 的全局变量(函数之外的变量)将不受它的印象,从而消除了负作用。

在 C 中是这样,但在 bash 中却不是。在 bash 中,每当在函数内部创建环境变量,就将其添加到 全局名称空间。这意味着,该变量将重写函数之外的全局变量,并在函数退出之后继续存在:

 #!/usr/bin/env bash

 myvar="hello"

 myfunc() {

     myvar="one two three"
     for x in $myvar
     do
         echo $x
     done
 }

 myfunc

 echo $myvar $x

 

运行此脚本时,它将输出 "one two three three",这显示了在函数中定义的 "$myvar" 如何影响全局变量 "$myvar",以及循环控制变量 "$x" 如何在函数退出之后继续存在(如果 "$x" 全局变量存在,也将受到影响)。

在这个简单的例子中,很容易找到该错误,并通过使用其它变量名来改正错误。但这不是正确的方法,解决此问题的最好方法是通过使用 "local" 命令,在一开始就预防影响全局变量的可能性。当使用 "local" 在函数内部创建变量时,将把它们放在 局部名称空间中,并且不会影响任何全局变量。这里演示了如何实现上述代码,以便不重写全局变量:


 #!/usr/bin/env bash

 myvar="hello"

 myfunc() {
     local x
     local myvar="one two three"
     for x in $myvar
     do
         echo $x
     done
 }

 myfunc

 echo $myvar $x

 

此函数将输出 "hello" — 不重写全局变量 "$myvar","$x" 在 myfunc 之外不继续存在。在函数的第一行,我们创建了以后要使用的局部变量 x,而在第二个例子 (local myvar="one two three"") 中,我们创建了局部变量 myvar, 同时 为其赋值。在将循环控制变量定义为局部变量时,使用第一种形式很方便,因为不允许说:"for local x in $myvar"。此函数不影响任何全局变量,鼓励您用这种方式设计所有的函数。只有在明确希望要修改全局变量时,才 应该使用 "local"。

Bash 实例,第一部分(转)

 linux  Bash 实例,第一部分(转)已关闭评论
3月 072013
 

转自:http://www.ibm.com/developerworks/cn/linux/shell/bash/bash-1/index.html

您可能要问:为什么要学习 Bash 编程?好,以下是几条令人信服的理由:

已经在运行它

如果查看一下,可能会发现:您现在正在运行 bash。因为 bash 是标准 Linux shell,并用于各种目的,所以,即使更改了缺省 shell,bash 可能  在系统中某处运行。因为 bash 已在运行,以后运行的任何 bash 脚本都天生是有效利用内存的,因为它们与任何已运行的 bash 进程共享内存。如果正在运行的工具可以胜任工作,并且做得很好,为什么还要装入一个 500K 的解释器?

 

已经在使用它

不仅在运行 bash,实际上,您每天还在与 bash 打交道。它总在那里,因此学习如何最大限度使用它是有意义的。这样做将使您的 bash 经验更有趣和有生产力。但是为什么要学习 bash 编程 ?很简单,因为您已在考虑如何运行命令、CPing 文件以及管道化和重定向输出。为什么不学习一种语言,以便使用和利用那些已熟悉和喜爱的强大省时的概念?命令 shell 开启了 UNIX 系统的潜能,而 bash 正是 这个 Linux shell。它是您和机器之间的高级纽带。增长 bash 知识吧,这将自动提高您在 Linux 和 UNIX 中的生产力 — 就那么简单。

 

Bash 困惑

以错误方式学习 bash 令人十分困惑。许多新手输入 "man bash" 来查看 bash 帮助页,但只得到非常简单和技术方面的 shell 功能性描述。还有人输入 "info bash"(来查看 GNU 信息文档),只能得到重新显示的帮助页,或者(如果幸运)略为友好的信息文档。

尽管这可能使初学者有些失望,但标准 bash 文档无法满足所有人的要求,它只适合那些已大体熟悉 shell 编程的人。帮助页中确实有很多极好的技术信息,但对初学者的帮助却有限。

这就是本系列的目的所在。在本系列中,我将讲述如何实际使用 bash 编程概念,以便编写自己的脚本。与技术描述不同,我将以简单的语言为您解释,使您不仅知道事情做什么,还知道应在何时使用。在此三部分系列末尾,您将可以自己编写复杂的 bash 脚本,并可以自如地使用 bash 以及通过阅读(和理解)标准 bash 文档来补充知识。让我们开始吧。

 

环境变量

在 bash 和几乎所有其它 shell 中,用户可以定义环境变量,这些环境变量在以 ASCII 字符串存储。环境变量的最便利之处在于:它们是 UNIX 进程模型的标准部分。这意味着:环境变量不仅由 shell 脚本独用,而且还可以由编译过的标准程序使用。当在 bash 中“导出”环境变量时,以后运行的任何程序,不管是不是 shell 脚本,都可以读取设置。一个很好的例子是 vipw 命令,它通常允许 root 用户编辑系统口令文件。通过将 EDITOR 环境变量设置成喜爱的文本编辑器名称,可以配置 vipw,使其使用该编辑器,而不使用 vi,如果习惯于 xemacs 而确实不喜欢 vi,那么这是很便利的。

在 bash 中定义环境变量的标准方法是:

$ myvar='This is my environment variable!'

 

以上命令定义了一个名为 "myvar" 的环境变量,并包含字符串 "This is my environment variable!"。以上有几点注意事项:第一,在等号 "=" 的两边没有空格,任何空格将导致错误(试一下看看)。第二个件要注意的事是:虽然在定义一个字时可以省略引号,但是当定义的环境变量值多于一个字时(包含空格或制表键),引号是必须的。

引用细节

有关如何在 bash 中使用引号的非常详尽的信息,请参阅 bash 帮助页面中的“引用”一节。特殊字符序列由其它值“扩展”(替换)确实使 bash 中字符串的处理变得复杂。本系列将只讲述最常用的引用功能。

第三,虽然通常可以用双引号来替代单引号,但在上例中,这样做会导致错误。为什么呢?因为使用单引号禁用了称为扩展的 bash 特性,其中,特殊字符和字符系列由值替换。例如,"!" 字符是历史扩展字符,bash 通常将其替换为前面输入的命令。(本系列文章中将不讲述历史扩展,因为它在 bash 编程中不常用。有关历史扩展的详细信息,请参阅 bash 帮助页中的“历史扩展”一节。)尽管这个类似于宏的功能很便利,但我们现在只想在环境变量后面加上一个简单的感叹号,而不是宏。

现在,让我们看一下如何实际使用环境变量。这有一个例子:

$ echo $myvar
This is my environment variable!

 

通过在环境变量的前面加上一个 $,可以使 bash 用 myvar 的值替换它。这在 bash 术语中叫做“变量扩展”。但是,这样做将怎样:

$ echo foo$myvarbar
foo

 

我们希望回显 "fooThis is my environment variable!bar",但却不是这样。错在哪里?简单地说,bash 变量扩展设施陷入了困惑。它无法识别要扩展哪一个变量:$m、$my、$myvar 、$myvarbar 等等。如何更明确清楚地告述 bash 引用哪一个变量?试一下这个:

$ echo foo${myvar}bar
fooThis is my environment variable!bar

 

如您所见,当环境变量没有与周围文本明显分开时,可以用花括号将它括起。虽然 $myvar 可以更快输入,并且在大多数情况下正确工作,但 ${myvar} 却能在几乎所有情况下正确通过语法分析。除此之外,二者相同,将在本系列的余下部分看到变量扩展的两种形式。请记住:当环境变量没有用空白(空格或制表键)与周围文本分开时,请使用更明确的花括号形式。

回想一下,我们还提到过可以“导出”变量。当导出环境变量时,它可以自动地由以后运行的任何脚本或可执行程序环境使用。shell 脚本可以使用 shell 的内置环境变量支持“到达”环境变量,而 C 程序可以使用 getenv() 函数调用。这里有一些 C 代码示例,输入并编译它们 — 它将帮助我们从 C 的角度理解环境变量:

myvar.c — 样本环境变量 C 程序

#include <stdio.h>
#include <stdlib.h>
int main(void) {
  char *myenvvar=getenv("EDITOR");
  printf("The editor environment variable is set to %sn",myenvvar);
}

 

将上面的代码保存到文件 myenv.c 中,然后发出以下命令进行编译:

$ gcc myenv.c -o myenv

 

现在,目录中将有一个可执行程序,它在运行时将打印 EDITOR 环境变量的值(如果有值的话)。这是在我机器上运行时的情况:

$ ./myenv
The editor environment variable is set to (null)

 

啊… 因为没有将 EDITOR 环境变量设置成任何值,所以 C 程序得到一个空字符串。让我们试着将它设置成特定值:

$ EDITOR=xemacs
$ ./myenv
The editor environment variable is set to (null)

 

虽然希望 myenv 打印值 "xemacs",但是因为还没有导出环境变量,所以它却没有很好地工作。这次让它正确工作:

$ export EDITOR
$ ./myenv
The editor environment variable is set to xemacs

 

现在,如您亲眼所见:不导出环境变量,另一个进程(在本例中是示例 C 程序)就看不到环境变量。顺便提一句,如果愿意,可以在一行定义并导出环境变量,如下所示:

$ export EDITOR=xemacs

 

这与两行版本的效果相同。现在该演示如何使用 unset 来除去环境变量:

$ unset EDITOR
$ ./myenv
The editor environment variable is set to (null)

 

dirname 和 basename

请注意:dirname 和 basename 不是磁盘上的文件或目录,它们只是字符串操作命令。

 

截断字符串概述

截断字符串是将初始字符串截断成较小的独立块,它是一般 shell 脚本每天执行的任务之一。很多时候,shell 脚本需要采用全限定路径,并找到结束的文件或目录。虽然可以用 bash 编码实现(而且有趣),但标准 basename UNIX 可执行程序可以极好地完成此工作:

$ basename /usr/local/share/doc/foo/foo.txt
foo.txt
$ basename /usr/home/drobbins
drobbins

 

Basename 是一个截断字符串的极简便工具。它的相关命令 dirname 返回 basename 丢弃的“另”一部分路径。

$ dirname /usr/local/share/doc/foo/foo.txt
/usr/local/share/doc/foo
$ dirname /usr/home/drobbins/
/usr/home

 

 

命令替换

需要知道一个简便操作:如何创建一个包含可执行命令结果的环境变量。这很容易:

$ MYDIR=`dirname /usr/local/share/doc/foo/foo.txt`
$ echo $MYDIR
/usr/local/share/doc/foo

 

上面所做的称为“命令替换”。此例中有几点需要指出。在第一行,简单地将要执行的命令以 反引号 括起。那不是标准的单引号,而是键盘中通常位于 Tab 键之上的单引号。可以用 bash 备用命令替换语法来做同样的事:

$ MYDIR=$(dirname /usr/local/share/doc/foo/foo.txt)
$ echo $MYDIR
/usr/local/share/doc/foo

 

如您所见,bash 提供多种方法来执行完全一样的操作。使用命令替换可以将任何命令或命令管道放在 ` ` 或 $( ) 之间,并将其分配给环境变量。真方便!下面是一个例子,演示如何在命令替换中使用管道:

MYFILES=$(ls /etc | grep pa)
bash-2.03$ echo $MYFILES
pam.d passwd

 

 

象专业人员那样截断字符串

尽管 basename 和 dirname 是很好的工具,但有时可能需要执行更高级的字符串“截断”,而不只是标准的路径名操作。当需要更强的说服力时,可以利用 bash 内置的变量扩展功能。已经使用了类似于 ${MYVAR} 的标准类型的变量扩展。但是 bash 自身也可以执行一些便利的字符串截断。看一下这些例子:

$ MYVAR=foodforthought.jpg
$ echo ${MYVAR##*fo}
rthought.jpg
$ echo ${MYVAR#*fo}
odforthought.jpg

 

在第一个例子中,输入了 ${MYVAR##*fo}。它的确切含义是什么?基本上,在 ${ } 中输入环境变量名称,两个 ##,然后是通配符 ("*fo")。然后,bash 取得 MYVAR,找到从字符串 "foodforthought.jpg" 开始处开始、且匹配通配符 "*fo" 的 最长 子字符串,然后将其从字符串的开始处截去。刚开始理解时会有些困难,为了感受一下这个特殊的 "##" 选项如何工作,让我们一步步地看看 bash 如何完成这个扩展。首先,它从 "foodforthought.jpg" 的开始处搜索与 "*fo" 通配符匹配的子字符串。以下是检查到的子字符串:

f
fo      MATCHES *fo
foo
food
foodf
foodfo      MATCHES *fo
foodfor
foodfort
foodforth
foodfortho
foodforthou
foodforthoug
foodforthought
foodforthought.j
foodforthought.jp
foodforthought.jpg

 

在搜索了匹配的字符串之后,可以看到 bash 找到两个匹配。它选择最长的匹配,从初始字符串的开始处除去,然后返回结果。

上面所示的第二个变量扩展形式看起来与第一个相同,但是它只使用一个 "#" — 并且 bash 执行 几乎 同样的过程。它查看与第一个例子相同的子字符串系列,但是 bash 从初始字符串除去 最短 的匹配,然后返回结果。所以,一查到 "fo" 子字符串,它就从字符串中除去 "fo",然后返回 "odforthought.jpg"。

这样说可能会令人十分困惑,下面以一简单方式记住这个功能。当搜索最长匹配时,使用 ##(因为 ## 比 # 长)。当搜索最短匹配时,使用 #。看,不难记吧!等一下,怎样记住应该使用 '#' 字符来从字符串开始部分除去?很简单!注意到了吗:在美国键盘上,shift-4 是 "$",它是 bash 变量扩展字符。在键盘上,紧靠 "$" 左边的是 "#"。这样,可以看到:"#" 位于 "$" 的“开始处”,因此(根据我们的记忆法),"#" 从字符串的开始处除去字符。您可能要问:如何从字符串末尾除去字符。如果猜到我们使用美国键盘上紧靠 "$"右边 的字符 ("%),那就猜对了。这里有一些简单的例子,解释如何截去字符串的末尾部分:

$ MYFOO="chickensoup.tar.gz"
$ echo ${MYFOO%%.*}
chickensoup
$ echo ${MYFOO%.*}
chickensoup.tar

 

正如您所见,除了将匹配通配符从字符串末尾除去之外,% 和 %% 变量扩展选项与 # 和 ## 的工作方式相同。请注意:如果要从末尾除去特定子字符串,不必使用 "*" 字符:

MYFOOD="chickensoup"
$ echo ${MYFOOD%%soup}
chicken

 

在此例中,使用 "%%" 或 "%" 并不重要,因为只能有一个匹配。还要记住:如果忘记了应该使用 "#" 还是 "%",则看一下键盘上的 3、4 和 5 键,然后猜出来。

可以根据特定字符偏移和长度,使用另一种形式的变量扩展,来选择特定子字符串。试着在 bash 中输入以下行:

$ EXCLAIM=cowabunga
$ echo ${EXCLAIM:0:3}
cow
$ echo ${EXCLAIM:3:7}
abunga

 

这种形式的字符串截断非常简便,只需用冒号分开来指定起始字符和子字符串长度。

 

应用字符串截断

现在我们已经学习了所有截断字符串的知识,下面写一个简单短小的 shell 脚本。我们的脚本将接受一个文件作为自变量,然后打印:该文件是否是一个 tar 文件。要确定它是否是 tar 文件,将在文件末尾查找模式 ".tar"。如下所示:

mytar.sh — 一个简单的脚本

#!/bin/bash
if [ "${1##*.}" = "tar" ]
then
    echo This appears to be a tarball.
else
    echo At first glance, this does not appear to be a tarball.
fi

 

要运行此脚本,将它输入到文件 mytar.sh 中,然后输入 "chmod 755 mytar.sh",生成可执行文件。然后,如下做一下 tar 文件试验:

$ ./mytar.sh thisfile.tar
This appears to be a tarball.
$ ./mytar.sh thatfile.gz
At first glance, this does not appear to be a tarball.

 

好,成功运行,但是不太实用。在使它更实用之前,先看一下上面使用的 "if" 语句。语句中使用了一个布尔表达式。在 bash 中,"=" 比较运算符检查字符串是否相等。在 bash 中,所有布尔表达式都用方括号括起。但是布尔表达式实际上测试什么?让我们看一下左边。根据前面所学的字符串截断知识,"${1##*.}" 将从环境变量 "1" 包含的字符串开始部分除去最长的 "*." 匹配,并返回结果。这将返回文件中最后一个 "." 之后的所有部分。显然,如果文件以 ".tar" 结束,结果将是 "tar",条件也为真。

您可能会想:开始处的 "1" 环境变量是什么。很简单 — $1 是传给脚本的第一个命令行自变量,$2 是第二个,以此类推。好,已经回顾了功能,下面来初探 "if" 语句。

 

If 语句

与大多数语言一样,bash 有自己的条件形式。在使用时,要遵循以上格式;即,将 "if" 和 "then" 放在不同行,并使 "else" 和结束处必需的 "fi" 与它们水平对齐。这将使代码易于阅读和调试。除了 "if,else" 形式之外,还有其它形式的 "if" 语句:

if [ condition ]
then
    action
fi

 

只有当 condition 为真时,该语句才执行操作,否则不执行操作,并继续执行 "fi" 之后的任何行。

if [ condition ]
then
    action
elif [ condition2 ]
then
    action2
.
.
.
elif [ condition3 ]
then
else
    actionx
fi

 

以上 "elif" 形式将连续测试每个条件,并执行符合第一个  条件的操作。如果没有条件为真,则将执行 "else" 操作,如果有一个条件为真,则继续执行整个 "if,elif,else" 语句之后的行。