为什么我的 bash 脚本中的 perl 正则表达式不起作用? MacOS 终端

为什么我的 bash 脚本中的 perl 正则表达式不起作用? MacOS 终端

我正在尝试在工作中放弃 AppleScripts 的工作流程,并创建一些可以在后台运行的更简单的东西。对于此任务,我每晚都会收到 35-40 个文件(7 或 8 个文件的 5 个不同质量的版本),并且我需要提取文件名的一部分。

例如,这些文件中的一批(缩写)可能如下所示:

每个文件有 5 个版本

    ab_12_345_01_dest_xxxxxxxxxx_640x360_1000.jpg
    ab_12_345_01_dest_xxxxxxxxxx_768x432_3000.jpg
    ab_12_345_01_dest_xxxxxxxxxx_960x540_5000.jpg
    ab_12_345_01_dest_xxxxxxxxxx_1280x720_7000.jpg
    ab_12_345_01_dest_xxxxxxxxxx_1920x1080_9000.jpg

这些文件都是这样命名的(使用最高版本,我稍后会解释为什么):

    ab_12_345_01_dest_xxxxxxxxxx_1920x1080_9000.jpg
    ab_12_345_02_dest_yyyyyyyyyy_1920x1080_9000.jpg
    ab_12_345_03_dest_zzzzzzzzzz_1920x1080_9000.jpg
    ab_12_345_part1_aaaaaaaaaa_1920x1080_9000.jpg
    ab_12_345_part2_bbbbbbbbbb_1920x1080_9000.jpg
    ab_12_345_part3_special_cccccccccc_1920x1080_9000.jpg
    ab_12_345_part4_dddddddddd_1920x1080_9000.jpg
    ab_12_345_04_dest_special_eeeeeeeeee_1920x1080_9000.jpg

所以我的目标是使用9000文件名的一部分来 grep 仅每个文件的最高版本(复制时间最长,所以如果它在那里,其余文件也在那里),然后提取所有内容到倒数第二_。到目前为止,我已经能够完成第一部分,但无法完成第二部分。

当我这样做时,我只能得到最高版本的列表:

    $ ls | grep 9000
    ab_12_345_01_dest_xxxxxxxxxx_1920x1080_9000.jpg
    ab_12_345_02_dest_yyyyyyyyyy_1920x1080_9000.jpg
    ab_12_345_03_dest_zzzzzzzzzz_1920x1080_9000.jpg
    ab_12_345_part1_aaaaaaaaaa_1920x1080_9000.jpg
    ab_12_345_part2_bbbbbbbbbb_1920x1080_9000.jpg
    ab_12_345_part3_special_cccccccccc_1920x1080_9000.jpg
    ab_12_345_part4_dddddddddd_1920x1080_9000.jpg
    ab_12_345_04_dest_special_eeeeeeeeee_1920x1080_9000.jpg

然后我尝试ls | grep 9000 | perl -pe '/^.+(?=_.+_.+)/mg认为我会得到以下内容(基于每个在线 RegEx 测试人员,特别是我能找到的 Perl RegEx 测试人员所说的可行):

    $ ls | grep 9000 | perl -pe '/^.+(?=_.+_.+)/mg`
    ab_12_345_01_dest_xxxxxxxxxx
    ab_12_345_02_dest_yyyyyyyyyy
    ab_12_345_03_dest_zzzzzzzzzz
    ab_12_345_part1_aaaaaaaaaa
    ab_12_345_part2_bbbbbbbbbb
    ab_12_345_part3_special_cccccccccc
    ab_12_345_part4_dddddddddd
    ab_12_345_04_dest_special_eeeeeeeeee

然而,我得到了同样的结果,就好像我从未通过管道传输到 Perl 一样。我最初尝试使用 awk 来实现这一点,但是我输入的命令变得相当长,我认为 RegEx 可能是可行的方法。但是,我需要正向前瞻,以便让它在倒数第二个位置停止匹配_(而不是_从字符串的开头开始计数),并且__当我设置 .awk 时, awk 保留了最后一个{$NL=$(NL-1)=""; print $0}

答案1

使用你的perl命令,你总是打印该行,因为你有-p选择。匹配部分不做任何事情。

您想要-n并打印匹配的部分:

ls -1 *9000.jpg \
| perl -lne 'print $1 if /^(.+)(?=_.+_.+)/'

由于文件名可能有换行符,您应该修改它以读取零分隔的文件名,但在您的情况下可能不需要:

printf '%s\0' *9000.jpg \
| perl -lne 'INIT{ $/ = "\0"}; print $1 if /^(.+)(?=_.+_.+)/'

或者,在 for 循环中读取文件名,然后您可以仅使用 shell 参数扩展:

for f in *9000.jpg; do printf '%s\n' "${f%_*_*}"; done

这可能更适合您的任务。 (=>“不要在文件名上使用基于行的文本编辑工具。”@Kusalananda)

答案2

无需通过管道从 到lsgrep过滤您的文件列表,您只需

ls *9000.jpg

此外,grep它还会挑选出名称中其他位置恰好有 9000 的任何文件。

您的正则表达式没有问题,只有perl.使用grep你就会得到你想要的

ls *9000.jpg | grep -Po "^.+(?=_.+_.+)"

另一种方法可以是

find . -iname "*9000.jpg" -exec sh -c 'basename ${1%_*_*}' sh {} \;

find作用与ls

扩展会删除从倒数第二个字符到字符串末尾的字符,并${1%_*_*}删除结果中包含的文件路径。_basenamefind

构造

-exec sh -c `blah blah` sh {} \;

非常值得学习和使用find,@Kusalananda 有一篇很好的文章这里

-exec只是告诉find对其输出执行“blah blah”,\;意味着对每个结果分别执行“blah blah”,这sh -c 'put some script in here'就是您想要对结果执行的操作,最后sh {}将输出传递find回中定义的脚本sh -c

相关内容