如何用 shell 脚本优雅的遍历文件夹内所有文件名的中文字符并替换? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
RayGZJ
V2EX    Linux

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

  •  
  •   RayGZJ Sep 4, 2022 via iPhone 4184 views
    This topic created in 1334 days ago, the information mentioned may be changed or developed.
    因为在收集学生的报名考试资料,但有些班主任不是完全按照我发的格式命名文件照片,目前我已将所有证件照都移动至一个文件夹内,我需要身份证号命名照片
    如:
    32000000000000123.jpg
    …..

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

    32000000000000123 李四.jpg

    用了正则脚本\u4E00-\u9FA5 还是不行 是我打开方式问题吗?
    Supplement 1    Sep 5, 2022

    各位看官,由此问题延伸出来的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://itsoss.com/elvish-shell/

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

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

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

    实际操作后会删除身份证号最后一位字母位 X
    RayGZJ
        5
    RayGZJ  
    OP
       Sep 4, 2022 via iPhone
    @RayGZJ
    @RayGZJ
    @yfugibr
    不带 X 的删除正常
    Jirajine
        6
    Jirajine  
       Sep 4, 2022   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  
       Sep 4, 2022
    带 X 换成 '[^0-9X]'
    RayGZJ
        8
    RayGZJ  
    OP
       Sep 4, 2022 via iPhone
    @Jirajine 感谢老师回复
    哈哈哈为了好看用的 ozm
    yfugibr
        9
    yfugibr  
       Sep 4, 2022
    @RayGZJ #5 忘了还有这个
    ```
    rename -v 's/.*(\d{17}[\dxX]).*\.(.*?)/$1.$2/' * -n
    ```
    这个应该没问题了
    RayGZJ
        10
    RayGZJ  
    OP
       Sep 4, 2022 via iPhone
    @yfugibr 谢谢你老师
    问题解决啦,我要好好学习正则

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

    这一块是功能实现的正则吗
    yfugibr
        11
    yfugibr  
       Sep 4, 2022
    @RayGZJ #10 对,正则主要是里面的
    ```
    .*(\d{17}[\dxX]).*\.(.*?)
    ```
    可以找语法看看,还是比较简单的
    wxf666
        12
    wxf666  
       Sep 4, 2022
    这样?

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

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

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


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

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

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


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

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


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

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

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

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

    > rename 自己又发明了一种 dsl 语言
    rename 哪里用的是 DSL 了,这玩意儿用的就是 perl 正则。用过 sed 的人就能无缝上手……
    Jirajine
        29
    Jirajine  
       Sep 5, 2022
    @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  
       Sep 5, 2022
    @aloxaf sed 的那个 script language 难道不是 dsl 吗?
    aloxaf
        31
    aloxaf  
       Sep 5, 2022
    @Jirajine #30 是,但是你说那是 rename 发明的就不对了
    aloxaf
        32
    aloxaf  
       Sep 5, 2022
    @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  
       Sep 5, 2022
    @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  
       Sep 5, 2022
    @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  
       Sep 5, 2022
    就这也能起争执,666
    wxf666
        36
    wxf666  
       Sep 5, 2022
    @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  
       Sep 6, 2022
    @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  
       Sep 6, 2022
    @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  
       Sep 6, 2022
    @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  
       Sep 6, 2022
    @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  
       Sep 6, 2022
    你俩竟然还在争论

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

    @Jirajine #39

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

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

    @wxf666 #40

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

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

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


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


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

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


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

    反正被折磨多后,一般都用 "..." 包裹住就是了(然而还有个 ! 。。)
    Jirajine
        43
    Jirajine  
       Sep 6, 2022
    @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  
       Sep 6, 2022   1
    @aloxaf 屎上雕花 这个形容太贴切了。bash 确实比 zsh 简单不少,但特性也不少(当然是必要的),不同的是 bash 是在 POSIX sh 上雕花,而 zsh 是同时在 POSIX sh 、bash 、ksh 上雕花。可能因为使用 bash glob 的时候还得同时考虑怎么 quoting 、是否含空格,我才感觉它复杂吧。
    About     Help     Advertise     Blog     API     FAQ     Solana     1628 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 73ms UTC 16:17 PVG 00:17 LAX 09:17 JFK 12:17
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86