V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
RayGZJ
V2EX  ›  Linux

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

  •  
  •   RayGZJ · 2022-09-04 23:04:15 +08:00 via iPhone · 3586 次点击
    这是一个创建于 802 天前的主题,其中的信息可能已经有所发展或是发生改变。
    因为在收集学生的报名考试资料,但有些班主任不是完全按照我发的格式命名文件照片,目前我已将所有证件照都移动至一个文件夹内,我需要身份证号命名照片
    如:
    32000000000000123.jpg
    …..

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

    32000000000000123 李四.jpg

    用了正则脚本\u4E00-\u9FA5 还是不行 是我打开方式问题吗?
    第 1 条附言  ·  2022-09-05 16:20:04 +08:00

    各位看官,由此问题延伸出来的Shell脚本讨论,有相同正则及shell实现问题可看如下回复 rename CLI 解决楼主问题方案请看 9楼回复的帖子

    #rename CLI Mac可通过brew安装
    rename -v 's/.*(\d{17}[\dxX]).*\.(.*?)/$1.$2/' * -n 
    

    正则表达式验证网址:https://regex101.com/

    以下内容涉及到由各行业大佬对于shell脚本的讨论 关键词:正则、rename、elvish、zsh、shell 、脚本语言、shell传参、bash

    elvish shell官网:https://elv.sh/

    elvish Quick look:https://itsfoss.com/elvish-shell/

    ohmyzsh官网:https://ohmyz.sh/

    延伸讨论: https://www.v2ex.com/t/877843 不限 shell 类型的情况下,用 shell 脚本可以实现哪些骚操作?

    44 条回复    2022-09-06 23:13:39 +08:00
    RayGZJ
        1
    RayGZJ  
    OP
       2022-09-04 23:06:47 +08:00 via iPhone
    有 rename 的 CLI 工具 但是 man rename 是在太干 也考虑 python 实现或许更简单些?
    yfugibr
        2
    yfugibr  
       2022-09-04 23:08:06 +08:00
    ```
    rename -v 's/.*(\d+).*\.(.*?)/$1.$2/' * -n
    ```
    确定没问题就去掉 -n 参数
    yfugibr
        3
    yfugibr  
       2022-09-04 23:10:06 +08:00   ❤️ 1
    @yfugibr #2 纠正一下
    ```
    rename -v 's/.*?(\d+).*?\.(.*?)/$1.$2/' * -n
    ```
    RayGZJ
        4
    RayGZJ  
    OP
       2022-09-04 23:13:53 +08:00 via iPhone
    @yfugibr 感谢回复

    实际操作后会删除身份证号最后一位字母位 X
    RayGZJ
        5
    RayGZJ  
    OP
       2022-09-04 23:14:41 +08:00 via iPhone
    @RayGZJ
    @RayGZJ
    @yfugibr
    不带 X 的删除正常
    Jirajine
        6
    Jirajine  
       2022-09-04 23:14:51 +08:00   ❤️ 4
    用 elvish ,比 bash 顺手,也比 Python 方便:
    put **.jpg | peach {|name|
    use re
    var number = (re:replace '[^0-9]' '' $name)
    mv $name $number.jpg
    }
    peach 并发执行,换成 each 顺序执行。
    Jirajine
        7
    Jirajine  
       2022-09-04 23:17:08 +08:00
    带 X 换成 '[^0-9X]'
    RayGZJ
        8
    RayGZJ  
    OP
       2022-09-04 23:20:23 +08:00 via iPhone
    @Jirajine 感谢老师回复
    哈哈哈为了好看用的 ozm
    yfugibr
        9
    yfugibr  
       2022-09-04 23:22:14 +08:00
    @RayGZJ #5 忘了还有这个
    ```
    rename -v 's/.*(\d{17}[\dxX]).*\.(.*?)/$1.$2/' * -n
    ```
    这个应该没问题了
    RayGZJ
        10
    RayGZJ  
    OP
       2022-09-04 23:25:10 +08:00 via iPhone
    @yfugibr 谢谢你老师
    问题解决啦,我要好好学习正则

    s/.*(\d{17}[\dxX]).*\.(.*?)/$1.$2/

    这一块是功能实现的正则吗
    yfugibr
        11
    yfugibr  
       2022-09-04 23:29:37 +08:00
    @RayGZJ #10 对,正则主要是里面的
    ```
    .*(\d{17}[\dxX]).*\.(.*?)
    ```
    可以找语法看看,还是比较简单的
    wxf666
        12
    wxf666  
       2022-09-04 23:33:06 +08:00
    这样?

    rename -n -v 's/^\s*.*?\s*(\d+[\dxX])\s*.*?(\.[^.]+)$/$1$2/' *

    一样,确定没问题就去掉 -n 参数
    wxf666
        13
    wxf666  
       2022-09-04 23:35:43 +08:00
    也对,名字里没有 0-9 x X ,直接 's/^.*?(\d+[\dxX]).*?(\.[^.]+)$/$1$2/' 就好
    wxf666
        14
    wxf666  
       2022-09-04 23:39:18 +08:00
    @Jirajine 这个为啥又比 bash 顺手了?

    上次有人说 powershell (在交互式下)比 bash 强大,是因为参数显式,严谨安全。。
    Jirajine
        15
    Jirajine  
       2022-09-04 23:54:41 +08:00
    @wxf666 语法现代、没有 shell 那么多乱七八糟 word splitting 之类的糟粕,支持 namespace 。
    支持 list/map 结构化数据类型,同时完美兼容传统的*nix 命令行工具和 byte pipe 。
    顺不顺手,你直接看我上面写的,还有官网 elv.sh 上的示例,可读性比长串正则+特殊语法好的多,同时也不像 pwsh 那么冗长命名。
    pwsh 就是个 dotnet script ,当 shell 实在是无比糟糕,命名冗长、过度 OOP ,外部命令是二等公民,管道完全不能用,只让你用 cmdlet 。
    wxf666
        16
    wxf666  
       2022-09-05 00:00:46 +08:00
    @Jirajine 我感觉你那个示例。。我更愿意写一行 rename + 正则。。
    wxf666
        17
    wxf666  
       2022-09-05 00:15:01 +08:00   ❤️ 1
    @Jirajine 另外,你那个示例的逻辑,可以写成:rename 's/[^\dxX]//g; s/$/.jpg/' *


    我觉得,交互式下的 shell ,简短快捷,还是很重要的吧

    word splitting 、特殊语法 也是为这个目的服务的

    不搞这些,就不可避免地会写长


    简短 和 美观,感觉不可兼得,就看个人喜好了

    反正交互式下,我是愿意阅读 bash 规则,牺牲一定可读性,来换取输入时的便捷的


    就好比有人会放弃易读的拼音,练习五笔,去换取快捷打字一样(我折中一下,学了个双拼。。)
    aloxaf
        18
    aloxaf  
       2022-09-05 00:57:11 +08:00
    来用 zsh

    autoload -Uz zmv
    zmv -n '*.jpg' '${f//[^0-9X]/}.jpg'
    haoliang
        19
    haoliang  
       2022-09-05 01:44:44 +08:00
    正巧我打算换个 shell ,看到楼上对 elvish 、bash 、zsh 的示例,我决定试试 elvish ,哈哈。
    虽然我的 login shell 是 zsh ,但我从来都只写 bash 脚本,复杂的就换 python 了。可以说 zsh 是我最熟悉的陌生人,它的大部分功能我都没用过。elvish 用 go 实现,方便阅读实现、定制,相见恨晚啊!
    Jirajine
        20
    Jirajine  
       2022-09-05 02:37:33 +08:00
    @wxf666 你上面的那种长正则,可读性很差的,不常写正则的人不用 regex101.com 这样的工具都看不懂。
    rename 自己又发明了一种 dsl 语言,不熟悉的人用起来有额外的心智负担。而且你还得同时处理 shell/rename 自己的语言 /regex 的转义,再配上 glob 。

    word splitting 可不是简短,是历史包袱,导致所有变量引用都得用引号括起来。
    特殊语法如 man zshexpn 看一看,正常编程语言简单的字符串处理有多麻烦。

    elvish 也是为交互式设计的,完全符合简短快捷易输入,PowerShell 才是故意搞得冗长、难以输入并美其名曰“可读性”的。
    Jirajine
        21
    Jirajine  
       2022-09-05 02:43:33 +08:00
    @wxf666 那个示例这样写便于阅读和复制粘贴,交互式输入把变量 inline 一下也只有一行。use re 是导入包,相当于 import 。
    mrfox
        22
    mrfox  
       2022-09-05 03:35:28 +08:00
    前不久听过 elvish ,去官网看了下,网上也搜索了下没找到什么教程
    挺难上手的还是
    wxf666
        23
    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
        24
    laqow  
       2022-09-05 10:40:58 +08:00
    把所有文件名 ls 出来,在文本文件中用懂的语言正则完确定没错了,再逐行 rename ori new
    webcape233
        25
    webcape233  
       2022-09-05 10:42:34 +08:00 via iPhone
    搞这么复杂,直接 grep 出来数字不接行了
    name=张三 12345
    new_name=$(echo $name |grep -oE "[0-9]+")
    mv $name $new_name
    webcape233
        26
    webcape233  
       2022-09-05 10:45:04 +08:00 via iPhone
    有 x 正则再加一个 改成"[0-9]+[xX]"
    webcape233
        27
    webcape233  
       2022-09-05 10:46:48 +08:00 via iPhone
    疏忽了扩展名,可以把扩展名提取出来 ,后面再加上
    aloxaf
        28
    aloxaf  
       2022-09-05 10:51:18 +08:00
    @Jirajine #20

    > rename 自己又发明了一种 dsl 语言
    rename 哪里用的是 DSL 了,这玩意儿用的就是 perl 正则。用过 sed 的人就能无缝上手……
    Jirajine
        29
    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
        30
    Jirajine  
       2022-09-05 11:28:28 +08:00
    @aloxaf sed 的那个 script language 难道不是 dsl 吗?
    aloxaf
        31
    aloxaf  
       2022-09-05 11:30:17 +08:00
    @Jirajine #30 是,但是你说那是 rename 发明的就不对了
    aloxaf
        32
    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
        33
    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
        34
    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
        35
    hxy100  
       2022-09-05 16:22:14 +08:00
    就这也能起争执,666
    wxf666
        36
    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
        37
    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
        38
    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
        39
    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
        40
    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[*]}" 来着,和 $* "$*" 有不同。。
    aloxaf
        41
    aloxaf  
       2022-09-06 15:15:12 +08:00
    你俩竟然还在争论

    说实话传统 shell 再难用也没办法,地位已经根深蒂固了。新兴 shell 再吹也没用,无论如何都入不了主流(除了 pwsh 那种捆绑的)。

    @Jirajine #39

    > bash 就是语言表达能力不足,才不得不把 glob 做的更复杂

    bash 的 glob 说实话不算复杂,屎上雕花的 zsh 那才是……怎么说呢,每次看到那些占满整行的单条表达式,都令我对作者肃然起敬

    @wxf666 #40

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

    这样未必不出问题,毕竟 \n 也是个合法的,可能出现在任意地方的字符。

    我觉得 word splitting 糟糕的点主要在于——它是全局的、隐式的,这就导致了它成为坑人的好手。
    wxf666
        42
    wxf666  
       2022-09-06 17:21:30 +08:00
    @aloxaf 算交流吧,都一起骂了 bash 的难用的地方,@Jirajine 也介绍了 elvish 优越之处,我也有自己的保留意见


    感觉我还能接受新鲜事物。不写脚本的话,尝试换下 shell 也是可以的(反正就自己用而已。好用就赚,难用就换~)


    听你这么一说,会不会 @Jirajine 说的『 bash 的 glob 复杂』,其实是『 zsh 的 glob 复杂』的意思。。

    原本我思来想去,即使算上 extended glob ,bash 的 glob 也和复杂沾不上边儿才对。。


    elvish 不也是按照 \n 切割 (...) 嘛。。

    反正被折磨多后,一般都用 "..." 包裹住就是了(然而还有个 ! 。。)
    Jirajine
        43
    Jirajine  
       2022-09-06 23:08:00 +08:00
    @wxf666 shell 的 变量 expansion 可以说是最糟糕的设计之一,一个变量的值,与如何引用它有关。而 IFS 就是控制如何引用变量的方式,所以非常易错。每当你引用含空格字符串的时候,都得设置 IFS ,包括变量赋值、复制等,操作其他字符串的时候还得再改回去。
    elvish 在这方面和所有正常的编程语言一样:变量的值是固定的,不允许在引用变量时自定义各种奇奇怪怪的 evaluate 的方式,因而和 Python 等正常编程语言一样,evaluate 时不需要 quoting 。

    elvish 不会更改你的变量内容,只是为了让使用传统*nix 命令行工具的 byte 输入输出格式更方便,很多接受多个 string 对象管道输入的命令(包括 (..) 输出捕获),也会接受 byte 流,并默认将 byte 流按换行符分割后作为多个 string 输入。其实这也是传统*nix 命令行工具约定俗成的格式,只不过 evlish 里真正有类型了而已。默认相当于 from-lines ,对于需要其他格式的输入则可以用 from-json (从 byte 流读取输入并反序列化 json 成结构化对象)、slurp (从 byte 流读取所有数据并存入一个 string 对象里输出)等等。
    Jirajine
        44
    Jirajine  
       2022-09-06 23:13:39 +08:00   ❤️ 1
    @aloxaf 屎上雕花 这个形容太贴切了。bash 确实比 zsh 简单不少,但特性也不少(当然是必要的),不同的是 bash 是在 POSIX sh 上雕花,而 zsh 是同时在 POSIX sh 、bash 、ksh 上雕花。可能因为使用 bash glob 的时候还得同时考虑怎么 quoting 、是否含空格,我才感觉它复杂吧。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5955 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 02:04 · PVG 10:04 · LAX 18:04 · JFK 21:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.