|
bash的增强特性
作者:tinybit
查找满足一定条件的文件是一项繁重的劳动。两年前你可能在某个文件上工作
过,现在你很需要它。但你有成千上万个文件,其中很多是重名的(它们是同一个
文件的不同修订版)。你想知道哪个是新的,哪个是旧的,可是你的记忆已经模糊
了。怎么办?只有用 find 吗?我也用过 find,但一项简单的工作,用 find 做
起来就觉得特别累人,至少要多敲入很多字符(我非常讨厌总要敲入“-name”、
引号或反斜杠):
find . -name "*.c"
用 bash 的新特性,就没有那么麻烦了:
ls **/*.c
echo **/*.c
此处 **/ 看作一个整体,是目录通配符,能匹配零级或多级子目录。这是由
bash 来解释的,任何命令都可以利用这一方便之处。通配符本来就应当是 shell
的属性,本来就应当由 shell 来解释。其他程序还要定义通配符,这是一种重复
劳动,也破坏了统一性,不利于记忆、学习与使用。增强 shell 的通配符,使我
们在解决问题时有多种可选择的途径(依个人习惯而定)。对于简单的文件名查找
来说,用 ls 能做到的,当然比较亲切哟!
bash 很可能是世界上最好的 shell, 但目前它的文件名扩展能力远不如
zsh 强大。现在你可以得到增强了文件名扩展功能的 bash-2.04
http://metalab.unc.edu/pub/gnu/bash/bash-2.04.tar.gz
(压缩的,1708806 字节, 即 1.7 兆字节)
的补丁
bash_2000-12-16_diff.gz
(压缩的,60329 字节,即 60 千字节)
随同该补丁一起,Tinybit 还提供了傻瓜型的安装设置脚本程序
setup_bash
(5696 字节)
将上述三个文件下载到同一个目录下,进入该目录,然后发送如下一条命令
sh setup_bash
即自动开始解压、修补和编译。
本期提供的补丁使得 bash 的文件名扩展能力远远比 zsh 强大。任意复杂的
条件查询,能轻松完成你的任务。丰富的条件包括:
1. 文件的名字,有“?”“*”“[]”等;
2. 文件名的逻辑操作(“或者、并且”之类),有“+()”“@()”“*()”等;
3. 文件在任意级子目录之下,有“**/”“***/”等;
4. 文件的属性满足一定的条件,有“条件表达式:”;
5. 除了列出文件名,还要列出所选择的属性,有“:修饰符”;
6. 列出文件时,希望按照某种规则来排序,有“::排序依据)”;
以上这些涵盖了文件名查询的方方面面举例说明(但是对文件内容的查询,不在此
列——这由系统工具 grep 来实现)。
现在说说本次补丁对 bash-2.04 的增强。首先,**/ 和 ***/,以及广义的
“**-p/”、“ ***-p/”、“**.p/” 和 “***.p/” 都是目录名通配符。定义如
下:
**/ 匹配“*/”的零个或多个出现,其中 * 只匹配真正的目录,不匹配指向目录
的符号链接。
***/ 类似于 **/,也匹配指向目录的符号链接。
**-p/ 匹配“p/”的零个或多个出现,其中 p 是可以含有通配符但不含“/”的任
意表达式,p 只匹配真正的目录,不匹配指向目录的符号链接。
***-p/ 类似于 **-p/,也匹配指向目录的符号链接。
**.p/ 类似于 **-p/,但不跨越文件系统边界,也即忽略安装点(mount point)
之下的文件。
***.p/ 类似于 ***-p/,但不跨越文件系统边界,也即忽略安装点(mount point
)之下的文件。
除此之外,每个文件名表达式都可以附加一个“限定符(qualifiers)”表达式。限
定符不是通配符,不能放在通配符表达式中间,只能附加在一个完整的文件名表达
式之后。限定符用一个冒号(:)开头,用冒号的目的是醒目,它清楚地将限定符
引导出来。冒号后必须紧接左括号“(”,然后是实际的限定符,最后是右括号“
)”。格式和用法如下:
文件名表达式限定符)
其中“限定符”又由四部分构成:前缀,条件表达式,修饰符和排序依据:
文件名表达式<前缀><条件表达式>:<修饰符>:<排序依据>)
前缀和条件表达式之间没有分隔符。前缀是一些全局开关,每个开关由两个字符组
成。第一个字符必须是“+”或“-”,第二个字符必须是字母。前缀中不能含有空
格。前缀必须顶头放置,并且不允许被多余的括号括起来。目前实现了六个开关。
它们是:
+i 强制实行大小写不敏感(case-insensitive)查找和匹配,即不区分大小写。
-i 强制实行大小写敏感(case-sensitive)查找和匹配,即区分大小写。
+d 强制通配符*?[]可以匹配点文件(glob dot files)。
-d 强制通配符*?[]不能匹配点文件(not glob dot files)。
+g 强制对即使不含通配符的文件名字符串也进行 glob
-g 强制对不含通配符的文件名字符串不进行 glob 的操作
条件表达式用来限定文件类型,访问权限,时间戳等等。条件表达式是一个算
术表达式。当条件表达式取真值(非零值)时,该文件被 glob 接受;当条件表达
式取假值(为零)时,该文件被 glob 剔除。
算术表达式是一个由常数、属性变量、属性伪函数、运算符、括号等构成的字
符串。所有的常数、属性变量、属性伪函数都取值整数,类型为 long, 即长整型
。
常数的表示方式 定义
十进制 由1到9开头的数字串,后接可选的一个单字母修饰符。
八进制 由0开头的数字(0至7)串。
十六进制 由0x或0X开头的数字(0至9)字母(a至f, A至F)串。
日期 由1到9开头的数字串,后接~和一个可选的数字串。
十进制数的修饰符有 7 个, 表示倍率:
修饰符 倍率因子 意义 举例
w 7*24*60*60 一周的秒数 4w = 4*7*24*60*60
d 24*60*60 一天的秒数 3d = 3*24*60*60
h 60*60 一小时的秒数 10h = 10*60*60
m 60 一分钟的秒数 300m = 300*60
K 1024 文件大小的常
用度量单位,
无需解释
4K = 4*1024
M 1024*1024 2M = 2*1024*1024
G 1024*1024*1024 3G = 3*1024*1024*1024
日期可用例子来说明:19991231~235959 表示1999年12月31日23时59分59秒。又如
:20000101~ 和 20000101~00 和 20000101~0000 和 20000101~000000 都表示
2000年1月1日0时0分0秒。时分秒部分必须占2位、4位、6位,或者省略。年份必须
在 1970 至 2038 的区间内。日期的值也是一个整数,表示1970年以来所经过的秒
数。日期既然是整数,它可参加任何数值运算。
属性表列出了目前已经实现的属性变量和属性伪函数。属性名中含有括号的是
伪函数。不要被这个长长的列表吓坏了。变量虽然多,但很有规律,容易记住。双
字母变量都取逻辑值(0 或 1),一些变量名同 stat 结构的元素名相似。变量的
取名原则是:短名字使用频度高,长名字有助于记忆。如果你想通过一些典型例子
来快速了解,可以不看属性表,继续阅读下面的内容。
下表按优先级从大到小的顺序列出目前已经实现的运算符。注意一些运算符与
C 语言中的不同(“=”“.”“,”分别对应于 C 语言的“==”“&&”“||”)
,目的是节省一个字符的位置和有更好的视觉效果。又由于我们的变量都是属性,
只用于读取而不用于写入,所以不需要“++”“--”“+=”“-=”“*=”“/=”“
>>=”“<<=”“&=”“|=”“%=”“^=”这类运算符。注意逻辑非(!)有可能被
bash 当作提取命令历史的符号,在它后面立即跟一个空格就可正确地识别为逻辑
非。
运算符 意义
( ) 括号
! ~ + - 一元运算符(逻辑非,逐位非,正,负)
** 乘方,右结合的:2**3**3=2**(3**3)
* / % 乘,除,模(求余数的运算)
+ - 加,减
<< >> 左移,右移
< <= >= > 小于,小于或等于,大于或等于,大于
= != 等于(不要用“==”),不等于
& 逐位与
^ 逐位异或
| 逐位或
.(句号,小数点) 逻辑与(不要用“&&”)
,(逗号) 逻辑或(不要用“||”)
?: 三元运算符(条件表达式),同 C 语言
值得提醒一下程序员朋友,我们的量都是长整型,当运算溢出时,高位部分丢失。
举例来说,下列表达式都取零值,因而为假:
2**32
2**33
2**34
2**31+0x80000000
还要注意 2**31 等于 0x80000000, 是负数。另外,不要认为由 size 变量所表示
的文件长度总是 >= 0 的,因为我们的 size 是有符号的, 不是 unsigned long
型的。当文件很大(大于 0x7fffffff)时,size 是负的。
限定符的第三部分是修饰符。修饰符用来控制输出的格式。修饰符的格式比较
简单,是字符串常量与属性变量的任意混合。字符串常量可包含反斜杠转义序列(
\t, \n 等)以便输出特殊的控制字符。属性变量必须用括号括起来。属性变量还
可以附加一个可选的格式描述符。格式描述符的位置是在括号内部、属性变量的右
侧。格式描述符类似于 C 语言,必须以 % 开始,可选地以 d, i, o, u, x, X (
整数变量的情况) 或 s (字符串变量的情况) 结尾。格式描述符的其他特征都类似
于 C 语言,这里不赘述。
修饰符有什么用?一般情况下,你不需要它(所以你不用害怕那长长的属性名
)。当你对 ls -l 的输出格式感到不满时,你现在有办法了。譬如
echo -l *modestr)(nlink%4lu) (sizestr) (mdate)
(pathname)(linkarrow)(readlink)(typemark))
显示的日期包含了年月日时分秒完整的信息。如果要你写出这么长的一个表达式,
你可能恼羞成怒。但是你可以像 setup_bash 文件结尾处那样定义 ll 和 ls 命令
。这个 ll 命令很像熟知的“/bin/ls -l --color”命令,能根据文件的类型用
不同的颜色显示文件名。这里要说明的是,“function ls”之后要紧接着花括弧
“{”,不要插入一对圆括弧“()”。本次补丁修改了不带圆括弧的函数定义的语法
意义。如果一个函数在定义时省略了圆括弧,那么 shell 对该函数的参数不进行
glob 扩展。这样,该函数的参数中所包含的通配符可以被函数体内的语句捕捉到
。本次补丁还修改了 echo 内部命令,为它增加了 -C, -x, -l 和 -t 选项。-C
和 -x 类似于 /bin/ls 命令的 -C 和 -x 选项,对输出的项目进行对齐。-l 和
-t 分别表示用换行符及制表符来代替缺省的空格分隔符。还有一点要说明,一个
限定符表达式可以作为默认表达式而作用于(同一条命令中)该文件名表达式之后
的其余文件名表达式。基于这三点改进,我们的 ls 函数得以构造出来。请比较一
下 ll 的输出结果和 “/bin/ls -l”有何不同。“/bin/ls -l”显示的日期令人
深恶痛绝: 它将日期和时间混合得不像个样子,同时它又不显示秒数,使得你无法
直观地判断同一分钟内产生的两个文件的先后。ll 的输出显得整齐,便于阅读。
你可以按照你自己的需要修改 ls 函数。注意我们的 ls 函数只是使用了内部命令
echo ,没有使用 /bin/ls 程序文件。这个 ls 函数已经很接近 /bin/ls 的输出
格式,你可以完全用它而不再用 /bin/ls 了。注意即使你根本不用 /bin/ls 命令
,也万万不可删除它,因为其它程序要用它。我们的 ls 函数已经避免了第二期“
讨厌的ls”中所说的缺陷。我们当然不想同 /bin/ls 命令完全一样。
再举一例。大家都熟悉 shell 的条件测试“-f file”“-d file”“-r
file”“-w file”“-x file”等等,特别是有一个名叫“-s file”的,含义为
“文件长度非零”,这对于测试“文件是否为空”是很有用的。然而我们常常碰到
“文件长度是否大于100”之类的问题,怎么办呢?只好请 perl 帮忙喽!现在我
们有了修饰符,不用再麻烦 perl 了。下面的修饰符取出文件长度,忽略文件名等
等其他信息。“-gt”是 shell 原来已具备的“大于”操作符:
if [ filesize)) -gt 100 ]; then do_something; fi
怎么样?很棒吧!(顺便说,如果你刚刚准备投入 Linux,那么建议你熟练掌握
shell 程序设计)。我们现在可以模仿 /bin/ls 的几乎全部特征,而且我们的控
制更加灵活多变。比如说,/bin/ls -s 可以列出文件的大小,但这个大小是以块
数(blocks)来表示的,无法以字节数来表示(虽然 /bin/ls -l 是以字节数来表
示的,但是列出太多的项目,而且基本上无法控制这些项目的次序或个数)。我们
用
echo -C - :(blocks%4lu) (pathname)) *
可以达到与 /bin/ls -s 相同的效果,而
echo -C - :(:(sizestr) (pathname)) *
是以字节数来精确地表示文件的大小。
限定符的第四部分是排序符。排序符由任意多个(零个或更多)排序项组成。
每个排序项又有两部分组成,第一部分是个单字符,是四个字符“+”“-”“<”
“>”之一,分别表示“ASCII升序”“ASCII降序”“locale升序”“locale降序
”。第二部分是属性变量,并且不允许用括号将属性变量括起来。每个排序项的两
部分必须同时出现,不能够随意省略一半。没有排序符时,按照“<pathname”来
对待,即按照路径名升序排列,使用本地化的排序规则。如果有多个排序项,按照
从左到右的优先级来使用。即,如果第一项不能决定两个文件的先后,就看第二项
;如果第二项也不能决定两文件的顺序,就看第三项,依此类推。
限定符的四部分(前缀,条件表达式,修饰符,排序符)是相互独立的,并且
都是可选的。四部分可以同时出现,也可以全部省略;可以只出现任意一部分,任
意两部分或任意三部分。(你有没有“任意驰骋”的“自由”之感?)(Linux丁点
广场友情提供) |
|