我对 bash 还很陌生;我几乎可以使用简单的命令一次执行 1 个简单的管理任务。然而,我的任务是使用文本文件作为重命名源来重命名目录中的一些文件,并且非常感谢一些指示,因为我远远超出了我的能力范围。
让我解释:
New File Name.xlsx 0.1 000011F4.dat
New File Name.xlsx 0.2 000011F5.dat
New File Name.xlsx 0.3 000011F6.dat
New File Name.xlsx 0.4 000011F7.dat
New File Name.xlsx 0.5 000011F8.dat
New File Name.xlsx 0.6 000011F9.dat
我的源文本文件有点类似于上面的内容。目的是第一“列”是文件的新名称,中间是版本,第三列是当前文件名。
我需要重命名目录中的 .dat 文件,将它们更改为第一列中显示的名称。我还需要在每个文件的开头添加版本号 0.1、0.2 等。
我有几个问题:
文件中有空格是一个大问题吗?在每个文件字符串周围添加“”会更好吗?
基本上我不知道从哪里开始,任何帮助将不胜感激。正如您所看到的,它比通常的重命名稍微复杂一些,需要将版本列添加到文件名的开头和列表中的空白处。
答案1
这应该有效:
sh <(sed -r 's/^\s*(.*)\s+([0-9\.]+)\s+([0-9A-Z]{8}\.dat)\s*$/mv -iv \3 "\2 \1"/' files)
...files
源文件的名称在哪里。
其作用是将命令的结果传递给(shell)sed
的新实例,使用sh
流程替代。该命令的输出sed
是:
mv -iv 000011F4.dat "0.1 New File Name.xlsx"
mv -iv 000011F5.dat "0.2 New File Name.xlsx"
mv -iv 000011F6.dat "0.3 New File Name.xlsx"
mv -iv 000011F7.dat "0.4 New File Name.xlsx"
mv -iv 000011F8.dat "0.5 New File Name.xlsx"
mv -iv 000011F9.dat "0.6 New File Name.xlsx"
将该sed
命令拆开,它会搜索一个模式:
^
- 行的开头\s*
- 开头的任何空格(.*)
- 任意字符(括号中将结果存储到\1
)\s+
- 至少一个空白字符([0-9\.]+)
0-9
-和中至少之一.
(存储到\2
)\s+
- 至少一个空白字符([0-9A-Z]{8}\.dat)
0-9
-或中的 8 个字符A-Z
,后跟.dat
(存储到\3
)\s*
- 末尾有任何空格$
- 行尾
...并将其替换为mv -iv \3 "\2 \1"
,其中\1
to\3
是先前存储的值。如果您愿意,您可以在版本号和文件名的其余部分之间使用除空格之外的其他内容。
结果如下:
$ ls -l
total 60
-rw-rw-r-- 1 z z 0 Aug 8 14:15 000011F4.dat
-rw-rw-r-- 1 z z 0 Aug 8 14:15 000011F5.dat
-rw-rw-r-- 1 z z 0 Aug 8 14:15 000011F6.dat
-rw-rw-r-- 1 z z 0 Aug 8 14:15 000011F7.dat
-rw-rw-r-- 1 z z 0 Aug 8 14:15 000011F8.dat
-rw-rw-r-- 1 z z 0 Aug 8 14:15 000011F9.dat
-rw-rw-r-- 1 z z 222 Aug 8 13:47 files
$ sh <(sed -r 's/^\s*(.*)\s+([0-9\.]+)\s+([0-9A-Z]{8}\.dat)\s*$/mv -iv \3 "\2 \1"/' files)
`000011F4.dat' -> `0.1 New File Name.xlsx'
`000011F5.dat' -> `0.2 New File Name.xlsx'
`000011F6.dat' -> `0.3 New File Name.xlsx'
`000011F7.dat' -> `0.4 New File Name.xlsx'
`000011F8.dat' -> `0.5 New File Name.xlsx'
`000011F9.dat' -> `0.6 New File Name.xlsx'
$ ls -l
total 60
-rw-rw-r-- 1 z z 0 Aug 8 14:15 0.1 New File Name.xlsx
-rw-rw-r-- 1 z z 0 Aug 8 14:15 0.2 New File Name.xlsx
-rw-rw-r-- 1 z z 0 Aug 8 14:15 0.3 New File Name.xlsx
-rw-rw-r-- 1 z z 0 Aug 8 14:15 0.4 New File Name.xlsx
-rw-rw-r-- 1 z z 0 Aug 8 14:15 0.5 New File Name.xlsx
-rw-rw-r-- 1 z z 0 Aug 8 14:15 0.6 New File Name.xlsx
-rw-rw-r-- 1 z z 222 Aug 8 13:47 files
答案2
sed 's/^\(.*\.xlsx\) \+\([[:digit:]]\+\.[[:digit:]]\+\) \+\(.[^ ]*\)/"\3" "\2\1"/' \
<file_list | xargs -n 2 mv
这会将行分为 之前的部分.xlsx
,这是新名称的第二部分,可以通过 访问\1
。它获取版本并将其分配给\2
.然后是旧文件名,忽略尾随空格。
这被引用并提供给mv
作为参数。确保-n 2
接收mv
两个参数:旧文件名和新文件名。
这些空格不会造成任何问题,使问题变得复杂的是您的输入列表结构不佳。如果要交换列并引用文件名,您可以只使用xargs
and mv
,而无需事先进行操作。
答案3
文件名中的空格以及某些列之间使用多个空格使这变得更加困难,但绝不是不可克服的。
逐行读取列表文件。通常人们会使用while IFS= read -r; do …
,但在这里去除前导和尾随空格可能会更稳健。对于每行:
- 将每一行分成三部分。一种方法是使用正则表达式匹配。
[[:space:]]+
匹配一个或多个空白字符(空格或制表符);[[:space:]]+
匹配一个或多个非空白字符。可以通过BASH_REMATCH
变量检索带括号的组。
另一种方法,这里不太方便,是使用${VAR##PATTERN}
和${VAR%PATTERN}
分别从变量中去除前缀或后缀。 - 最后执行动作。不要忘记记录任何错误。
把它们放在一起:
ret=0
while read line; do
if [[ $line =~ (.*[^[:space:]])[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+) ]]; then
new_name="${BASH_REMATCH[1]}"
version="${BASH_REMATCH[2]}"
old_name="${BASH_REMATCH[3]}"
mv -- "$old_name" "$version$new_name" || ret=1
else
echo "Malformed line: $line"
fi
done <name_list.txt
exit $ret
答案4
解决方案awk
是运行以下命令:
awk '{print "/bin/mv", $NF, "\"" $(NF-1), gensub(/^([^.]+\.xlsx).*/, "\\1", 1) "\"" | "bash" } ; END { close("bash") }' sourcefile
前面的命令将bash
命令的输出传递给 shell:
awk '{print "/bin/mv", $NF, "\"" $(NF-1), gensub(/^([^.]+\.xlsx).*/, "\\1", 1) "\""}' sourcefile
应该首先运行它以确保它确实是您想要执行的!此awk
命令为源文件中的每一行打印该/bin/mv
命令,后跟该行中最后一个以空格分隔的字段,后跟双引号,后跟该行中倒数第二个字段,然后是将整行替换为字符串中的所有内容.xlsx
,后跟双引号的结果。
这是您可能更喜欢的变体:
awk '{print "/bin/mv", $NF, "\"" "0." FNR, gensub(/^([^.]+\.xlsx).*/, "\\1", 1) "\"" | "bash" } ; END { close("bash") }' sourcefile
该变量FNR
是行号(因此您可以从源文件中省略条目 0.1、0.2、0.3,...)。
文件名中的空格并不是我所说的“大问题”,但我建议不要这样做。您可以使用类似最终版本的内容,它将新文件名中的空格更改为下划线:
awk '{print "/bin/mv", $NF, "0." FNR "_" gensub(" ","_", "g", gensub(/^([^.]+\.xlsx).*/, "\\1", 1)) | "bash" } ; END { close("bash") }' sourcefile