重命名多个文件,通过“_”拆分文件名并保留第一个和最后一个字段

rename multiple files splitting filenames by '_' and retaining first and last fields

假设我有以下文件:

a_b.txt               a_b_c.txt             a_b_c_d_e.txt         a_b_c_d_e_f_g_h_i.txt

我想重命名它们,将它们的文件名分开 _ 并保留第一个和最后一个字段,所以我最终得到:

a_b.txt               a_c.txt             a_e.txt         a_i.txt

以为会很简单,但我有点卡住了...

我尝试 rename 使用以下正则表达式:

rename 's/^([^_]*).*([^_]*[.]txt)/_/' *.txt

但我真正需要做的是实际拆分文件名,所以我想到了awk,但我不太熟练......这是我目前所拥有的(我知道在某些时候我应该指定 FS="_" 并以某种方式获取第一个和最后一个字段...

find . -name "*.txt" | awk -v mvcmd='mv "%s" "%s"\n' '{old=[=13=]; <<split by _ here somehow and retain first and last fields>>; printf mvcmd,old,[=13=]}'

有什么帮助吗?我没有首选的方法,但是用这个来学习会很好awk。谢谢!

您的 rename 尝试接近;你只需要确保最后一组是贪婪的。

rename 's/^([^_]*).*_([^_]*[.]txt)$/_/' *_*_*.txt

我在最后一个左括号前添加了一个_(这是关键修复),在末尾添加了一个$锚点,并且还扩展了通配符,这样你就不会处理任何不包含至少两个下划线的文件。

Awk 中的等价物可能类似于

find . -name "*_*_*.txt" |
awk -F _ '{ system("mv " [=11=] " "  "_" $(NF)) }'

由于 system 调用,这有点脆弱;如果您的文件名可能包含空格或其他 shell 元字符,您可能需要重新考虑您的方法。您可以添加引号以部分修复该问题,但如果文件名包含文字引号,则该命令将失败。你也可以解决这个问题,但这样对我来说有点太复杂了。

这里有一个不那么脆弱的方法,它应该可以处理完全任意的文件名,甚至是其中包含换行符的文件名:

find . -name "*_*_*.txt" -exec sh -c 'for f; do
    mv "$f" "${f%%_*}_${f##*_}"
  done' _ {} +

find 将在每个文件名之前提供一个前导路径,因此我们在这里不需要 mv --(永远不会有以破折号开头的文件名)。

parameter expansion ${f##pattern} 生成变量 f 的值,其中 pattern 上的最长可用匹配从头开始修剪; ${f%%pattern} 做同样的事情,但从字符串的末尾开始修剪。

这个答案适用于您的示例,但@tripleee 的“查找”方法更可靠。

for f in a_*.txt; do mv "$f" "${f%%_*}_${f##*_}"; done

详情:https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html / https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html

使用您展示的示例,请尝试遵循纯 bash 代码(具有 BASH 的强大使用参数扩展能力)。这将捕获名称中带有 name/format .txt 的所有文件。然后它不会选择像这样的文件:a_b.txt它只会根据要求选择名称中有超过 1 个下划线的文件。

for file in *_*_*.txt
do
   firstPart="${file%%_*}"
   secondPart="${file##*_}"
   newName="${firstPart}_${secondPart}"
   mv -- "$file"  "$newName"
done

一个不同的 rename 正则表达式

rename 's/(\S_)[a-z_]*(\S\.txt)//'

使用与 sed 相同的正则表达式或在循环中使用 awk

for a in a_*; do 
    name=$(echo $a | awk -F_ '{print , $NF}'); #Or
    #name=$(echo $a | sed -E 's/(\S_)[a-z_]*(\S\.txt)//g');  
    mv "$a" "$name"; 
done

这是给定示例的替代正则表达式:

$ rename -n 's/_.*_/_/' *.txt
rename(a_b_c_d_e_f_g_h_i.txt, a_i.txt)
rename(a_b_c_d_e.txt, a_e.txt)
rename(a_b_c.txt, a_c.txt)