使用 tr, rename, bash 内置变量修改文件名

有如下所示的目录,现要将目录中的 '-" 改成 "_"
$ ll
total 12
drwxrwxr-x 2 jaseywang jaseywang 4096 2012-07-29 15:09 1-2-3/
drwxrwxr-x 2 jaseywang jaseywang 4096 2012-07-29 15:09 4-5-6/
drwxrwxr-x 2 jaseywang jaseywang 4096 2012-07-29 15:09 7-8-9/

g 了如下几种方式,没找到源出处,侵权了可以联系我。

可以通过 tr 实现:
$ find . -type d -name "*-*" -print | while read name; do na=$(echo $name | tr '-' '_'); if [[ $name != $na ]]; then mv "$name" $na; fi; done

原来的脚本是替换掉文件名中的空格:
$ find . -type f -name "* *" -print |
> while read name; do
> na=$(echo $name | tr ' ' '_')
> if [[ $name != $na ]]; then
> mv "$name" $na
> fi
> done

除了通过上面的 tr 实现,还有个更简单的方式,使用 rename,支持正则:
$ rename 's/-/_/g' *

下面是 rename 一些常见的用法,均来自互联网,找不到出处,不再解释了,应该都能看得懂,其实就是一些正则的运用:
$ ls
1.txt 2.txt 3.txt 4.txt
$ rename 's/\.txt/\.ext/' *
$ ls

1.ext 2.ext 3.ext 4.ext

$ ls
1.txt 2.txt 3.txt 4.txt
$ rename 's/\.txt//' *
$ ls

1 2 3 4

$ ls
1 2 3 4
$ rename 's/$/\.txt/' *
$ ls

1.txt 2.txt 3.txt 4.txt

$ ls
1.ext 2.ext 3.ext 4.ext
$ rename 's/(\d)/第$1 章/' *
$ ls

第 1 章.ext 第 2 章.ext 第 3 章.ext 第 4 章.ext

CU 上找到了个很牛逼的方式,通过 bash 的变量扩展来实现:

当然那个脚本有个小错误,正确的应该如下:
$ for file in *_2007-12-11*.log;do mv $file "${file%-*}-15_${file##*_}";done

关于 bash 的内置变量,主要通过 # 以及 % 这两个符号实现,分别在 $ 的左右,因此就比较好记忆了,前者表示去头,后者表示去尾;一个 #/% 表示最小匹配、连续两个 ##/%% 表示最大匹配。解释如下:
${varible##*string} 从左向右截取最后一个 string 后的字符串
${varible#*string}从左向右截取第一个 string 后的字符串
${varible%%string*}从右向左截取最后一个 string 后的字符串
${varible%string*}从右向左截取第一个 string 后的字符串
${varible:n1:n2}:截取变量 varible 从 n1 到 n2 之间的字符串。

可以看看下面几个小实验:

$ variable="hello jaseywang"

$ echo ${#variable}
15

$ echo ${variable:2}
llo jaseywang

$ echo ${variable:2:4}
llo

$ variable='/home/jaseywang/tmp'
$ echo ${variable#/}

home/jaseywang/tmp

$ echo ${variable#*/}
home/jaseywang/tmp

$ echo ${variable##*/}
tmp

$ echo ${variable%/*}
/home/jaseywang

$ echo ${variable%%/*}

$ echo ${variable/\//\\}
\home/jaseywang/tmp

$ echo ${variable//\//\\}
\home\jaseywang\tmp

学完了上面的,下面来看我们最开始需要解决的问题。以 1-2-3 这个文件为例,可以将其分为分为 3 部分,分别取出 1, 2, 3 这三个字符:
tmp1=${file%%-*}

tmp=${file%-*}
tmp2=${tmp#*-}

tmp3=${file##*-}

然后写个完整的语句就可以了:
$ for file in *;do tmp1=${file%%-*};tmp=${file%-*};tmp2=${tmp#*-};tmp3=${file##*-};mv $file  "$tmp1"_"$tmp2"_"$tmp3";done
$ ll

total 12
drwxrwxr-x 2 jaseywang jaseywang 4096 2012-07-29 15:09 1_2_3/
drwxrwxr-x 2 jaseywang jaseywang 4096 2012-07-29 15:09 4_5_6/
drwxrwxr-x 2 jaseywang jaseywang 4096 2012-07-29 15:09 7_8_9/

显然通过 awk 也可以实现,但这类外置的命令操作的效率会比内置的操作慢很多。