简体   繁体   English

bash shell:更改多个文件名以添加前导零

[英]bash shell: change multiple file names to add leading zeros

I have several files with this pattern: prefix.1.* , prefix.2.* , prefix.3.* , etc... and I want their name to be changed, respectively, to prefix.01.* , prefix.02.* , prefix.03.* , etc. With that, they will be properly sorted by name as there are filenames with already two digits (eg prefix.27.* ) in the set.我有几个具有这种模式的文件: prefix.1.*prefix.2.*prefix.3.*等......我希望它们的名称分别更改为prefix.01.*prefix.02.* , prefix.03.*等。有了这个,它们将按名称正确排序,因为集合中已经有两位数字的文件名(例如prefix.27.* )。

How can I do that using commands available in the bash shell?如何使用 bash shell 中可用的命令来做到这一点?


Note: Just after this issue, I had to cope with a list of files like prefix.1.* , prefix.2.* , prefix.17.* , prefix.157.* (arbitrary, not sequentially numbered), with the aim to convert to prefix.001.* , prefix.002.* , prefix.017.* , prefix.157.* , with a generic way of inserting the right number of leading zeros.注意:就在这个问题之后,我不得不处理像prefix.1.*prefix.2.*prefix.17.*prefix.157.* (任意,不按顺序编号)这样的文件列表,旨在转换为prefix.001.* , prefix.002.* , prefix.017.* , prefix.157.* ,使用插入正确数量的前导零的通用方法。 If you face this situation, I strongly recommend you to follow the general solution provided by mklement0 answer ( More generic renaming solutions ) below.如果您遇到这种情况,我强烈建议您遵循下面mklement0 答案更通用的重命名解决方案)提供的通用解决方案

for file in prefix.[0-9].*
do
   mv "$file" "${file/prefix./prefix.0}"
done

More generic renaming solutions , prompted by later comments by the OP:更通用的重命名解决方案,由 OP 稍后评论提示:

Uses the Perl-based rename utility , which comes standard on some Linux distros (eg, Ubuntu).使用基于 Perl 的rename实用程序,它是某些Linux 发行版(例如,Ubuntu)的标准配置。 Caveat : some distros have a different utility of the same name from the util-linux package.警告:某些发行版具有与util-linux软件包同名的不同实用程序。
On OS X, you can install via Homebrew using brew install rename .在 OS X 上,您可以使用brew install rename通过Homebrew brew install rename

Both solutions determine the required padding width dynamically .两种解决方案都可以动态确定所需的填充宽度。


Simpler solution, in case files are already numbered sequentially and only padding must be performed [does NOT meet the OP's requirements - see other solution below] :更简单的解决方案,如果文件已经按顺序编号并且必须仅执行填充[不满足 OP 的要求 - 请参阅下面的其他解决方案]

printf '%s\n' prefix.[0-9]*.* |                # enumerate files of interest
  sort -t. -n -k2,2 |                          # sort them by the embedded number
    rename -n -N '...01' -e 's/\.\d+\./.$N./'  # rename with appropriate padding
  • -n performs a dry-run only, showing only what would happen; -n执行空运行,只显示发生什么; remove it to perform actual renaming删除它以执行实际重命名
  • -N '...01' specifies the format for the special counter variable $N in a way that auto-determines appropriate zero-padding based on the number of input files . -N '...01'指定特殊计数器变量$N的格式,其方式是根据输入文件的数量自动确定适当的零填充
  • 's/\\.\\d+\\./.$N./' is a Perl expression that replaces the existing number component with the appropriately padded counter variable $N . 's/\\.\\d+\\./.$N./'是一个 Perl 表达式,它用适当填充的计数器变量$N替换现有的数字组件。

More sophisticated solution that preserves existing numbers and merely pads them.保留现有数字并仅填充它们的更复杂的解决方案。

In this case, the padding width is derived from the largest existing number embedded in the filenames :在这种情况下,填充宽度来自嵌入在文件名中最大现有数字

# Determine the longest (largest) number contained in the matching filenames...
maxNum=$(printf '%s\n' prefix.[0-9]*.* | sort -nrt. -k2,2 | head -1 | cut -d. -f2)
# ... and derive the padding length from it.
padLength=${#maxNum}

# Perform renaming by zero-padding the *existing* numbers in the filenames.
rename -n -e 's/\.(\d+)\./sprintf(".%0'${padLength}'s.", $1)/e' prefix.[0-9]*.*
  • -n performs a dry-run only, showing only what would happen; -n执行空运行,只显示发生什么; remove it to perform actual renaming删除它以执行实际重命名
  • Note the use of Perl's e flag in the s/.../.../ command, which allows use of an arbitrary Perl expression in the replacement string;注意在s/.../.../命令中使用 Perl 的e标志,它允许在替换字符串中使用任意 Perl 表达式; in this case, an sprintf expression is used to zero-pad the input number;在这种情况下, sprintf表达式用于对输入数字进行零填充; credit for this approach belongs to @Adrian Frühwirth's answer here .这种方法的功劳属于@Adrian Frühwirth在此处的回答。

On a general note, matching filenames could be made more robust with shopt -s extglob and prefix.+([0-9]).* ;一般而言,使用shopt -s extglobprefix.+([0-9]).*可以使匹配的文件名更加可靠; similarly, shopt -s nullglob would make it easier to determine whether any files matched.同样, shopt -s nullglob可以更轻松地确定是否有任何文件匹配。

Note a pure bash solution, but a little more concise(*):注意纯bash解决方案,但更简洁一些(*):

printf '%s\0' prefix.[0-9].* | 
  xargs -0 -I % bash -c 'f="%"; mv "$f" "${f/\./.0}"'

This assumes that glob prefix.[0-9].* expands to at least 1 actual filename.这假设 glob prefix.[0-9].*扩展为至少 1 个实际文件名。
If it's possible that NO files match, execute shopt -s nullglob first (and, if required, restore that shell option's value later).如果可能没有文件匹配,请先执行shopt -s nullglob (如果需要,稍后恢复该 shell 选项的值)。


Performance note :性能说明

While this is more concise than @R Sahu's solution , it will perform worse , due to spawning an additional child shell ( bash -c ) for every input filename - so as to be able to assign the filename to a variable and use expansion on it.虽然这比@R Sahu 的解决方案简洁,但它的性能会更差,因为为每个输入文件名生成一个额外的子 shell ( bash -c ) - 以便能够将文件名分配给一个变量并对其使用扩展.


(*) In terms of character count, @R Sahu's solution is actually shorter, but the absence of a loop (and fewer lines) may result in this answer being perceived as shorter. (*) 在字符数方面,@R Sahu 的解决方案实际上更短,但没有循环(和更少的行)可能会导致这个答案被认为更短。 At the end of the day, this solution doesn't add much in itself - except perhaps as an advanced example of using xargs :归根结底,这个解决方案本身并没有增加太多 - 除了作为使用xargs的高级示例

  • printf '%s\\0' ensures that all matching filenames are passed through the pipeline with NUL chars. printf '%s\\0'确保所有匹配的文件名都通过管道以 NUL 字符传递。 as separators.作为分隔符。
  • xargs -0 then ensures that the filenames are recognized individually, even if they contain embedded whitespace. xargs -0然后确保文件名被单独识别,即使它们包含嵌入的空格。
  • -I % ensures that each input filename results in its own invocation of the command that follows, with % instances replaced with the filename at hand. -I %确保每个输入文件名都会导致其对随后的命令的调用,并将%实例替换为手头的文件名。
  • bash -c then invokes a child shell (a child process that happens to be another bash instance) and evaluates the specified string. bash -c然后调用一个子 shell(一个恰好是另一个bash实例的子进程)并计算指定的字符串。

我发现的最简单的方法是在 bash shell 中输入这一行:

for ITER in 1 2 3 4 5 6 7 8 9; do for FILE in prefix."$ITER".*; do mv "$FILE" $(echo "$FILE" | sed 's/prefix.'$ITER'/prefix.0'$ITER'/'); done; done

One more way>>另一种方式>>

#!/bin/bash
count=1
for i in $(ls prefix*)
do
Newname=prefix\.$(printf "%02d" $count)\.txt
mv $i $Newname
count=$(($count + 1))
done

Great answers so far.到目前为止很好的答案。 If the extension is same of all the files.如果所有文件的扩展名相同。 Just use following.只需使用以下。 Here the extension is .jpg you can change prefix_ to your choice of prefix.这里的扩展名是.jpg您可以将prefix_更改为您选择的前缀。 Also if you want more leading zeros just change %02 to %03 and so on.此外,如果您想要更多前导零,只需将 %02 更改为 %03 等等。

rename -n -e 's/\d+/sprintf("prefix_%02d",$&)/e' -- *.jpg

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM