在 Unix 环境下进行字符串处理,整体能力确实不算特别强大。与那些内置 length() 等现成函数的高级语言不同,Unix 中要操作字符串,往往需要借助 expr 命令配合通配符来间接实现。虽然过程稍显繁琐,但只要掌握了每种处理方式的规律与常见陷阱,对症下药,依然可以找到简洁高效的方法来解决日常的字符串操作需求。
一、对用户输入的字符串长度进行验证
系统工程师经常需要校验用户输入的字符串长度。例如编写登录程序时,出于安全考虑,通常会要求密码长度至少为六位以上。这种场景本质上就是统计输入字符串的长度:不达标则提示,达标则存储。实现时可通过 if 或 while 循环配合条件判断,而条件部分则交由 expr 关键字来执行。
要判断用户输入的密码长度,可以这样编写命令:
expr "$userpasswd" : '.*'
使用这条命令需要注意几个关键规则。首先,expr 需要两个表达式,中间用英文冒号分隔。冒号左侧是要处理的字符串(变量),右侧是正则表达式。顺序非常严格,写反了将无法正常使用。其次,待处理的字符串必须用双引号括起来——这与普通命令中变量的用法不同。平时变量不加引号,加引号反而被视为常量;但在这里,变量一定要用双引号包裹,否则结果会出错。许多工程师都在这上面吃过亏。最后,右侧的正则表达式通常要用单引号引起来,例如 '.*' 表示匹配所有字符。若不添加单引号,系统很可能报语法错误。
二、截取字符串
有时需要截取字符串的子串来完成特定任务。比如系统工程师希望根据系统时间为文件命名,规则是年份后两位加月份,例如 0906 表示 09 年 6 月。每月生成一个单独的日志文件,这种命名方式相当合理。但实际中,用 date 命令获取年份时,得到的往往是四位数字(如 2009),而非两位。那么如何只取后两位呢?
此时可以借助 expr 命令的另一个功能——截取子串。通常来说,expr 在字符串处理上主要有三种用途:求长度、截子串、查位置。如果与其他命令组合,还能实现更复杂的操作。不过在 Unix 系统中,能直接用自带命令解决的问题,应优先使用自带命令。例如 date %w 可以直接显示今天是星期几,完全没必要用 expr 去折腾。只有当系统没有现成工具时,才考虑使用 expr 这个万能方案。虽然它能完成所有截取工作,但代码写起来不如自带命令那么简洁。
回到例子,要截取年份的后两位,可以借助转义字符这样编写:
$expr "2009" : '..\(..\)'
这里的 \ 是转义字符,正则表达式的含义是只取字符串的最后两个字符,忽略其他部分。如果字符串来源于变量,只需将 2009 替换为变量名即可。实际进行文件命名时,先截取年份后两位,再拼接上月份,就能顺利完成。
难点在于正则表达式的编写。只要正则写对,expr 的功能不亚于数据库中的字符串函数,甚至更加灵活。但经验不足的工程师往往在这里卡壳——甚至老手也偶尔会犹豫。想提升正则功底,最有效的方法是多看他人编写的脚本,模仿多了自然就能熟练运用。
三、修改文件的扩展名
假设某个目录下有很多 .sh 文件(Unix 配置文件,本质上就是文本),现在需要将所有扩展名改为 .txt。手动逐个修改效率太低,批量处理才是正确思路。
批量改名可以借助 for 循环来实现。循环体内有两种思路。第一种是使用 expr 命令:文件名本质上就是字符串,先用 expr 截取扩展名之前的部分,存入变量 filenameshort,然后通过 mv 或 rename 重命名为 $filenameshort.txt。但问题在于文件名长度不固定,正则表达式编写起来很麻烦,要将不带扩展名的部分完整截取出来,难度较大。
这里介绍一个更合适的命令——basename。它同样是一个字符串处理工具,基本格式为 basename text1 text2,功能是从第一个字符串中去掉第二个字符串的内容。例如 basename setup.sh sh 会返回 setup.(去掉 sh 后的结果)。用它来批量改名就简单多了:在循环中调用 basename 去掉原扩展名,再拼接上新的扩展名,完全不需要编写正则表达式。代码清晰且易于维护。
从这个案例可以总结一个基本原则:无论截取字符串还是其他操作,应优先查找系统自带命令(例如 date 的选项参数);如果没有,再考虑 basename 这类无需正则的工具;实在不行,才使用 expr。原因很简单——正则表达式虽然强大,但编写时容易出错,尤其在复杂场景下。用于测长度尚可,真要干截取、定位的精细活,没有一定功力很难一次写对。
