如何用 shell 脚本优雅的遍历文件夹内所有文件名的中文字符并替换?

2022-09-04 23:04:15 +08:00
 RayGZJ
因为在收集学生的报名考试资料,但有些班主任不是完全按照我发的格式命名文件照片,目前我已将所有证件照都移动至一个文件夹内,我需要身份证号命名照片
如:
32000000000000123.jpg
…..

而实际文件名是这样的
文件内容
张三 32000000000000123.jpg

32000000000000123 李四.jpg

用了正则脚本\u4E00-\u9FA5 还是不行 是我打开方式问题吗?
3588 次点击
所在节点    Linux
44 条回复
Jirajine
2022-09-05 02:43:33 +08:00
@wxf666 那个示例这样写便于阅读和复制粘贴,交互式输入把变量 inline 一下也只有一行。use re 是导入包,相当于 import 。
mrfox
2022-09-05 03:35:28 +08:00
前不久听过 elvish ,去官网看了下,网上也搜索了下没找到什么教程
挺难上手的还是
wxf666
2022-09-05 10:38:33 +08:00
@Jirajine

> 用 elvish ,……,也比 Python 方便

直接和 Python 比不公平吧。。

后者定位是脚本语言,你前者拿来当交互式 shell 用的。。

Python 也有个 shell 实现,xonsh ,你试过吗?感觉咋样?


> 长正则,可读性差,有人看不懂

看各人咯,不想写正则,多半也是自己写代码,模拟实现了正则的逻辑出来

可能写的多了后,厌烦了,也会转正则那边去了


> rename 自己又有一种 dsl 语言

你是说很多命令有自己风格的正则嘛?

确实是个问题。但好像也就几种风格:posix bre ere 、pcre

\d 不支持就试试 [0-9] 或 [[:digit:]] 呗。反正支持的正则特性都差不多


> shell rename 转义,glob

shell 里用 'pattern' 来表达 rename 的正则,也没啥麻烦吧

glob ?*.jpg ?有啥问题么。。


> word splitting 可不是简短,是历史包袱

我觉得 bash 为实现下列功能,整体花费的代价很小。elvish 是如何实现的?

1. 变量 /subshell 捕获,被 split 成多个参数( bash:$s ,$(xxx))

2. 被作为一个参数传递( bash:"str: $s","captured: $(xxx)")


> man zshexpn 看一看特殊语法,字符串处理有多麻烦

我只用过 bash ,没用过 zsh 。bash 支持的字符串处理确实不多


> PowerShell 才是故意搞得冗长、难以输入并美其名曰“可读性”的

反正那人坚持说 powershell 因为冗长而强大。bash 简短易出错,容易友尽、吃牢饭等。。

另一个人说 shell 命令简短,容易记混 ln ls ll du dd df ……
laqow
2022-09-05 10:40:58 +08:00
把所有文件名 ls 出来,在文本文件中用懂的语言正则完确定没错了,再逐行 rename ori new
webcape233
2022-09-05 10:42:34 +08:00
搞这么复杂,直接 grep 出来数字不接行了
name=张三 12345
new_name=$(echo $name |grep -oE "[0-9]+")
mv $name $new_name
webcape233
2022-09-05 10:45:04 +08:00
有 x 正则再加一个 改成"[0-9]+[xX]"
webcape233
2022-09-05 10:46:48 +08:00
疏忽了扩展名,可以把扩展名提取出来 ,后面再加上
aloxaf
2022-09-05 10:51:18 +08:00
@Jirajine #20

> rename 自己又发明了一种 dsl 语言
rename 哪里用的是 DSL 了,这玩意儿用的就是 perl 正则。用过 sed 的人就能无缝上手……
Jirajine
2022-09-05 11:24:44 +08:00
@wxf666 这里说的是脚本,因为 Python 不是 shell ,调用命令即使使用 sh 这样库也不如 shell 直接。xonsh 就是个玩具,oil/osh 倒是一个类 Python 的 shell ,但 UI 比 elvish 差太多,而且是用一套自制工具链写的。

像你那个 rename ,用$1 $2 来引用 capture group 就是个自己的语言,这玩意不是 shell 变量,elvish 及其他高级编程语言都不需要多此一举,一致性更好。

用 shell 构建另一个语言的字符串,你得考虑 shell 的转义(单引号或$符号)、dsl 的转义( /符号)、正则特殊字符的转义,glob 又是一套类似正则,但又不同的匹配语言,太多不一致性和心智负担了。

word splitting 是因为 POSIX sh 一切皆 string ,缺乏 list 类型,导致 IFS 的 trick 和引用变量总是需要 quoting 。用 shell 处理包含空格的字符串简直是 nightmare 。

elvish 是支持结构化数据类型的,string 永远是 string ,不会被自动 split 也不需要 quoting 。你要传多个参数,直接用 list 就行了,subshell ( elvish 里是 output capture )可以像 golang 一样多返回值,只要输出多个值自然就是多个参数。
var a b = (put 1; put 2)
echo $a $b c 等价于 echo (put 1; put 2) c

bash 和 zsh 一样,也有一大堆晦涩的 expansion 语法,难记难写,不过 zsh 是最“强大”的,支持的功能最多,从 sh 到 bash 到 csh 再到 zsh 原创的,也是最混乱的。

elvish 也是崇尚简短的,你看内置函数的命名,一个单词或一个单词加一个介词,命名风格也没有需要 Shift 才能输入的符号,如 to-json 对比 PowerShell 里 ConvertTo-Json 。
Jirajine
2022-09-05 11:28:28 +08:00
@aloxaf sed 的那个 script language 难道不是 dsl 吗?
aloxaf
2022-09-05 11:30:17 +08:00
@Jirajine #30 是,但是你说那是 rename 发明的就不对了
aloxaf
2022-09-05 11:35:43 +08:00
@Jirajine #29

> 像你那个 rename ,用$1 $2 来引用 capture group 就是个自己的语言,这玩意不是 shell 变量,elvish 及其他高级编程语言都不需要多此一举,一致性更好。
这话就有点罔顾事实了啊,用 $1 、$2 来引用 capture group 明明是通用做法,elvish 自己都是这么用的: https://elv.sh/ref/re.html#re:replace
wxf666
2022-09-05 12:25:00 +08:00
@Jirajine

> xonsh 就是个玩具

xonsh 大概有啥不足呢?我只匆匆看过一眼,没用过


> rename 用 $1 $2 来引用 capture group ,是自己的语言,不是 shell 的

用的是 pcre 吧。sed 也类似(但用的是 posix bre ere )

他们作为字符串传递,本来就和 shell 没啥关系呀

胶水语言就是这样咯,用最基础的 shell 语法,来描述要用人家的啥东西。。

我觉得你那个 elvish 应该也类似,golang 的正则库支持特性不多( regex101 说的)

想用一些高级特性(如 \p{Han}、a*?、a*+、(?>...)、(?<=...)、(?R)、(?(DEFINE)...) 等),就要导入其他库使用,甚至有自己的语法,不和 golang 未来的正则库兼容

这时候会不会就有人说 elvish 号称一致性,实际有多套正则语法,互不兼容,心智负担……


> 用 shell 构建另一个语言的字符串,你得考虑 shell 的转义(单引号或$符号)、dsl 的转义( /符号)、正则特殊字符的转义

shell 的转义:'$p $a $t \t \e \r \n' 都没问题啊(除了有 ' 字符时,需要用 'aaa'\''bbb' 表示)

dsl 的转义:那就看你那个工具的设计好不好咯。sed perl rename 可以用其他符号的:s|http://|https://|

正则特殊字符的转义:这个就是正则的问题了。golang 应该一样要面对


> glob 又是一套类似正则,但又不同的匹配语言

bash 的 glob ,也就 [] ? * {} 之类几个简单的语法吧,比正则简单得多

另外,我瞅了一眼,elvish 也是另外一套语法,且有点繁杂。。

bash:*.[ch]、[a-z].go

elvish:*.?[set:ch]、?[range:a-z].go


> 引用变量总是需要 quoting 。用 shell 处理包含空格的字符串简直是 nightmare

Emm... "$s" 不至于是 nightmare 吧。。


> 你要传多个参数,直接用 list 就行了

其实 bash 也支持 数组 和 哈希 呀。传多个参数,也可以直接用 数组:ls "${array[@]}"


> 你要传多个参数,直接用 list 就行了

我好奇 elvish 如何将 一个命令的返回值,split 成多个参数,传递给另一命令?如:

apt purge $(dpkg -l | grep ^rc | awk '{print $2}')
Jirajine
2022-09-05 15:42:47 +08:00
@wxf666 parser 太随意,一部分传给 python ,一部分传给 bash ,语义严重不一致。

还是因为缺乏结构化数据类型,只能一边 parse 字符串,一边拼接出特定的字符串给别的程序 parse 。

go/rust 的正则库大差不差,功能完全够了吧,其他“高级特性”实现的话就做不到线性时间复杂度了,而且考虑可读性也不适合用单条正则写。

关键是它标准库有常用字符串和正则函数,这些 bash 根本实现不了。

一堆不同语言的转义,考虑的太多,glob 和正则共用不少符号但语义又不一样,增加心智负担。

elvish 的 glob 简单很多,从文档的长度上就能体现出来。

bash 几乎总是需要 quoting ,如果处理含空格字符串的话,空格有时候作为分隔符,有时候又不是,还有 IFS ,处理起来难写难用且易错。

bash 的数组和 map 那是在 posix sh 上后来加的,二等公民,而且不能表达结构化数据(数组存 map 、map 存数组),也避免不了 word splitting 的糟粕。

你那个命令 elvish 是兼容的,直接
apt purge (dpkg -l | grep '^rc' | awk '{ print $2}')
效果是一样的。
因为 elvish 兼容传统 byte pipe ,期望 chan string 作为输入类型的命令 也会接受字节流输入,并把字节流按换行符 split 。output capture 也会默认做这种转换。 如果不希望这种转换可以用 slurp ,slurp 接受读取 byte pipe 输入原样存入一个 string 里返回。
hxy100
2022-09-05 16:22:14 +08:00
就这也能起争执,666
wxf666
2022-09-05 21:22:17 +08:00
@Jirajine 用了些 emoji 快速表明态度


> parser 太随意,一部分传给 python ,一部分传给 bash ,语义严重不一致。

我看它是,但凡 命令 和 参数 中,有出现未定义的标识符,就当作外部命令处理

感觉也说得过去?❓能举些你觉得容易出错的例子吗?


> 还是因为缺乏结构化数据类型,只能一边 parse 字符串,一边拼接出特定的字符串给别的程序 parse

elvish 能直接传递 list map 给其他命令?从其他命令接收来的 str ,不 parse ,咋变成 list map 的?

❓你是说 xml json 序列化、反序列化 这些库吗?


> go/rust 的正则库大差不差,功能完全够了吧

相比 pcre C# 而言,还差很多。😑你这样说,显得人家在不务正业。。


> 其他“高级特性”实现的话就做不到线性时间复杂度了

写应用确实速度快(是因为用 DFA 实现吗?)

但在 交互式 shell 中,更需要功能丰富啊

正则库不支持,❓不就要苦逼地写代码,自己实现相同逻辑了么。。


> 关键是它标准库有常用字符串和正则函数,这些 bash 根本实现不了

✔️确实,字符串处理是 bash 的弱项。这应该也属于 shell 的基础设施的

正则匹配的话,还是有个 [[ $s =~ regex ]] 可用的


> 一堆不同语言的转义,考虑的太多

🔁再次重复:

bash 的 'pattern' 足够好了,连 \ 都是本身的意思:'\'

最多只需考虑 ' ( elvish 也需写成 'str1''str2')


> glob 和正则共用不少符号但语义又不一样,增加心智负担。elvish 的 glob 简单很多

🔁再次重复:

elvish 的 glob 也用了 ? * ** {} ?[],❓语义不是也和正则不同吗?

❓elvish 的 glob 也比 bash 的复杂呀:

bash:*.[ch]、[a-z].go

elvish:*.?[set:ch]、?[range:a-z].go


> bash 几乎总是需要 quoting ,如果处理含空格字符串的话,空格有时候作为分隔符,有时候又不是,还有 IFS ,处理起来难写难用且易错。

需要作为一个参数:"$s"

需要 word splitting:$s

❓足以应付大多数场景了吧?或者你举些例子?


> bash 的 数组和 map ……不能表达结构化数据

✔️确实,但对于日常交互式使用而言,一般也足够了。再复杂,也适合上脚本了


> 默认按换行符 split 。如果不希望这种转换可以用 slurp

🤔还行

bash 作为一个参数:"$s"、"$(...)"

elvish 作为一个参数:$s 、(... | slurp)

❓还是感觉 bash 整体代价较低?

❓elvish 默认是按 \n 切割的。如何像 bash 那样,按 IFS 来切割 (...) 呢?
Jirajine
2022-09-06 07:32:29 +08:00
@wxf666 所以说它不是一个新语言,充其量只能算是类似 ipython 的 ui 。

结构化数据肯定只能在语言内部用,但 bash 是语言内部都没有结构化数据,你就得一直拿字符串糊过来糊过去。
ip --json addr | all (from-json) | each {|i| echo $i[ifname] $i[addr_info][0][local]}

正则搞得太“高级”,不符合 do one thing and do it well 的理念,这些功能自己写代码实现可读性和心智负担都更好,这也是 go/rust/cpp 等语言的正则库只实现一部分特性的原因。

quoting hell 了解一下,如果转义字符也需要转义。。

多打几个字符不代表复杂,看文档长度就知道哪个复杂了,elvish 可没有 extended glob ,特殊字符也很少。

word splitting 我举个最简单的例子:
一个目录里有两个文件:
Record.txt
Record 02.txt

a=$(ls) ; file $a # 这里是写$a 还是"$a"呢?
(我知道 ls 不该这样用,这里是演示处理含空格字符串)

日常使用结构化数据非常有用,尤其是数组,上面那个例子在 elvish 里就没有任何问题:
var a = (ls) ; file $a

word splitting 和 IFS 就是早年 sh 没有数组而发明的 trick 。

其他不需要 IFS 的语言怎么切割,这还用说吗?就像所有高级编程语言一样,用 str:split 函数啊。

另外 elvish 不会对结果做任何分割,是 (...)表达式本身可以 evaluate 成多个值,就像 golang 的多返回值一样。
wxf666
2022-09-06 11:18:42 +08:00
@Jirajine

> 充其量只能算是类似 ipython 的 ui

无所谓这些概念。🔁还是希望你能举些 xonsh 『语义严重不一致』的例子❓


> 但 bash 是语言内部都没有结构化数据,你就得一直拿字符串糊过来糊过去

✔️大部分同意。但如果只是 json 的话,jq 这个工具大部分时候还是足够使用的:

ip --json addr | jq -r '.[] | .ifname + " " + .addr_info[0].local'


> 正则搞得太“高级”,不符合 do one thing and do it well 的理念

我觉得还是『匹配某句法规则的字符串』范畴,且允许描述得更好了,还是符合理念的

换句话说,❓为何 go/rust 的正则库,就是按照“do one thing and do it well”理念设计的、shell 正则库的巅峰呢?

❓不能是 perl 的 pcre 嘛?


> 这些功能自己写代码实现可读性和心智负担都更好

🤔我不觉得我会自己实现 \p{Han}、a*?、a*+、(?>)、(?<!),心智负担很大。。

🤔也不觉得 [\u4e00-\u9fa5] 会比 \p{Han} 可读性好(其实前者只是后者的小子集,真实情况前者会很长)


> 这也是 go/rust/cpp 等语言的正则库只实现一部分特性的原因

🤔『线性时间复杂度』等特点,是高速应用的需求,但不该是交互式 shell 的


> quoting hell 了解一下,如果转义字符也需要转义。。

🔁再次重复:

'\' 中的转义字符,就是它自己,不是转义了 '。若要转义 ':

bash:'left'\''right'

elvish:'left''right'

实在不行,还有 Here Doc 嘛:

cat <<'EOF'
 !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
EOF


> elvish 可没有 extended glob ,特殊字符也很少

bash 默认也不开启 extended glob 。。

❓要不你举些 bash 的 glob 比 elvish 复杂的例子出来吧


> a=$(ls) ; file $a # 这里是写$a 还是"$a"呢?

IFS=$'\n'

file $(ls)

❓突然想到,这样是不是就和 elvish 的 (...) 等价了?


> 日常使用结构化数据非常有用,尤其是数组

✔️确实,这些也应该是 shell 的基础设施。bash 能用,但用起来很累:

readarray -t arr < <(ls)

file "${arr[@]}"
Jirajine
2022-09-06 13:09:56 +08:00
@wxf666 上下文敏感的 parser ,ls -al 是调用命令还是 算术表达式取决于上下文,这种不一致性在复杂情况下结果可想而知,变量名拼写错误会执行外部代码。

jq 就是糊字符串,简单读取一些 json field 倒还好,稍微复杂点的 group_by ,结构变换等就麻烦了,当然做肯定能做,毕竟 jq 自己也有一套“高级”的 dsl 。
脚本里有一大堆 ad-hoc 的 parse 和拼接字符串的代码,难写难读易错还不安全,远远不如直接操作结构化数据。

go/rust 的正则库都是从 RE2 来的,这种设计安全、高效、不会 overflow 、线性匹配时间,详见 https://swtch.com/~rsc/regexp/regexp1.html
Unicode script 和 lazy 匹配都是支持的,不支持的只有涉及 look around 和 backreference 的特性,完全不必要而且 overload 的字符更多让正则更难读。
quoting hell 指一次 evaluation 的结果再次被另一种相同或不同的规则 evaluate ,所以需要同时为多种规则 quoting 和转义。如:
ssh host touch 'my file.txt'
会创建 my 和 file.txt 两个文件。

bash 就是语言表达能力不足,才不得不把 glob 做的更复杂,另外涉及兼容性等原因有时开了有时又没开更加剧碎片化,zsh 在这方面更为尤甚。

这就是 IFS 和 word splitting 糟糕的地方,数据(变量值)影响语义,在代码的不同地方处理、存储、复制、传递含空格字符串,并保证结果正确是非常麻烦且恶心的。
bash 的 array 不是一般的难用,比如复制 array 到另一个变量:
a=('file 01.txt' 'file 02.txt')
b=$a
b=${a[*]} b="${a[*]}" b=${a[@]} b="${a[@]}"
b=( $a )
b=( ${a[*]} ) b=( "${a[*]}" ) b=( ${a[@]} )
以上这些 b 的值都是什么?
wxf666
2022-09-06 14:21:44 +08:00
@Jirajine

> 变量名拼写错误会执行外部代码

🤔我还没怎么用过 xonsh ,也不知这种情况严不严重


> 脚本里有一大堆 ad-hoc 的 parse 和拼接字符串的代码,难写难读易错还不安全,远远不如直接操作结构化数据

✔️写脚本我就不用 bash 这坑爹玩意儿了,换个 Python 不香嘛。。


> 正则

🤔我还是坚持:交互式 shell 需要功能强大的正则


> quoting hell 指一次 evaluation 的结果再次被另一种相同或不同的规则 evaluate ,所以需要同时为多种规则 quoting 和转义

✔️噢,这种啊,确实绕脑袋

❓elvish 是咋解决的?


> glob

🔁我还是不知道你说的,bash 的 glob 比 elvish 复杂,是啥意思。。❓可以举些例子嘛?


> 这就是 IFS 和 word splitting 糟糕的地方,数据(变量值)影响语义,在代码的不同地方处理、存储、复制、传递含空格字符串,并保证结果正确是非常麻烦且恶心的

🔁IFS=$'\n' 后,❓不就可以像 elvish 那样,不用处理啥空格了嘛?

需要一个参数时:"$s"、"$(...)"

需要每行作为一个参数时:$s 、$(...)


> bash 的 array 不是一般的难用

✔️知道 bash 的 array 坑多。反正我一般绕过那些奇奇怪怪的用法,老老实实 "${arr[@]}" 之类的。。

我记得好像 ${arr[*]} 还是 "${arr[*]}" 来着,和 $* "$*" 有不同。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/877692

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX