vak*_*ufo 2 shell bash filenames rename
有时我需要重命名一个目录中的所有文件(重命名约定在后面),其中文件名总是以“filenamename.extension”的形式(扩展名总是存在和变化)。该名称可能包含来自 [:graph:] 类的空格和字符。我的第一个问题是它应该在 *NIX 系统(尤其是 Linux、BSD、后来的其他系统,比如 AIX)之间绝对可移植。我的第二个问题是 [:graph:] 类。文件名可以是:
cat.txt
dog_and_cat.txt
Where is the cat?.png
my.cat.is.cute.txt.js.html
;;; ;;; ;;;.......321
áéú? _[a lot of whitespaces]_ óü^^^^^ö.jpg
Run Code Online (Sandbox Code Playgroud)
很容易看到,那些很难处理并放入 for 循环中。例如,
for i in *; do something; done
Run Code Online (Sandbox Code Playgroud)
并不总是喜欢空格和奇怪的字符,尤其是在不同的操作系统中。
重命名约定是将所有文件重命名为$FOOBAR.$EXTENSIONwhere$FOOBAR是某种散列形式,例如 md5sum。所以在 for 循环中我有一条线,就像
mv $FILE $(md5sum $FILE | sed 's/\ \ .\+//');
Run Code Online (Sandbox Code Playgroud)
它会将文件移动到自身的 md5sum,但扩展名消失了。我想保留扩展名,这些扩展名几乎总是在.[a-zA-Z0-9]{1,3}表单中。有时.tar.gz也需要保留类似的扩展(当然我可以将它们添加到变量中,例如MYEXTENSIONS='tar.gz tar.bz2 foo.bar')。
我的直觉告诉我,这个问题可以通过参数化的默认 UNIX/shell 命令解决,但现在对我来说非常困难。我相信我会从答案中学到很多东西。我知道我说的是神奇的词portability,但是如果我必须指定语言,则首选 bash 中的解决方案。
由于这是一个相当复杂的问题,我将提供一些指导原则:
$()必须像在这个结构外部一样被引用。不需要额外的转义。的$()和``构建体剥离后的新行,所以必须添加不同的字符,然后剥离它外面$()的结构:
varx="$([command which might print a value ending in \n]; echo x)"
var="${varx%x}"
Run Code Online (Sandbox Code Playgroud)--in 命令需要将参数与文件名分开,因为文件名可以以 开头--,因此将作为参数处理。
find不支持此语法,因此用于readlink获取根据定义以斜杠开头的绝对路径,或确保给定的路径find已经是绝对路径或以./.<(代替管道以避免在发送进程终止时损坏管道。cat或ssh吞下所有的贪婪命令。$'--$`!*@\a\b\E\f\r\t\v\\\'"\360\240\202\211 \n'实际上,for i in *; do something; done正确对待每个文件名,除了以 a 开头的文件名.被排除在通配符匹配之外。要可移植地匹配所有文件(除了.和..),匹配* .[!.]* ..?*并跳过由不匹配模式保持不变而导致的任何不存在的文件。
如果您遇到问题,那可能是因为您后来没有$i正确引用。始终在变量替换和命令替换周围放置双引号:"$foo","$(cmd)"除非您打算进行字段拆分和通配符。
如果您需要将文件名传递给外部命令(此处不需要),请注意echo "$foo"并不总是按$foo字面意思打印。一些shell执行反斜杠扩展,一些$foo开头的值-将被视为一个选项。准确打印字符串的安全且符合 POSIX 标准的方法是
printf '%s' "$foo"
Run Code Online (Sandbox Code Playgroud)
或printf '%s\n' "$foo"在末尾添加换行符。另一件需要注意的事情是命令替换会删除尾随的换行符;如果您需要保留换行符,一个可能的技巧是在数据中附加一个非换行符,确保转换保留这个字符,最后截断这个字符。例如:
mangled_file_name="$(printf '%sa' "$file_name" | tr -sc '[:alnum:]-+_.' '[_*]')"
mangled_file_name="${mangled_file_name%a}"
Run Code Online (Sandbox Code Playgroud)
要提取文件的 md5sum,请避免在md5sum输出中包含文件名,因为这会使剥离变得困难。在md5sum的标准输入上传递数据。
请注意,该md5sum命令不在 POSIX 中。一些 Unix 变体有md5或根本没有。cksum是 POSIX 但容易发生冲突。
让我们把它们放在一起(未经测试)。这里的一切都可以在任何 POSIX shell 下运行;您可以从 bash 功能中获得一点,但不会太多。
for old_name in * .[!.]* ..?*; do
if ! [ -e "$old_name" ]; then continue; fi
hash=$(md5sum <"$old_name")
case "$old_name" in
*.*.gz|*.*.bz2) # double extension
ext=".${old_name##*.}"
tmp="${old_name%.*}"
ext=".${old_name##*.}$ext";;
?*.*) ext=".${old_name##*.}";; # simple extension
*) ext=;; # no extension
esac
mv -- "$old_name" "$hash$ext"
done
Run Code Online (Sandbox Code Playgroud)
请注意,我没有考虑已经存在指定名称的目标文件的情况。特别是,如果您有现有文件,其名称看起来像您采用的约定,但校验和部分与文件的内容不匹配,而是与其他具有相同扩展名的文件的内容匹配,则发生的情况将取决于相对字典顺序文件名。
| 归档时间: |
|
| 查看次数: |
1521 次 |
| 最近记录: |