我是新手,想知道如何构建这个 shell 脚本:
我在目录 1 中有文件名A1-001.xyz A29-002.xyz A82-003.xyz
,我想根据文件名的第二部分将这些文件移动001 002 003
到目录 2 中,文件夹名称为 001 002 003。
这是我到目前为止所做的:
for file in /path/to/directory1/** ; do
echo "$file" | awk -F '[-]' '{print $2}' | cut -f 1 -d '.' ;
done >> dummy.txt
input="dummy.txt"
while IFS= read -r file; do
echo "$file" | mv "$file" /path/to/directory2/$file ;
done
Run Code Online (Sandbox Code Playgroud)
我的想法是将第一部分的输出文件名放入 dummy.txt 然后读取文件名并移动它。脚本的第二部分似乎不起作用,所以有什么关于如何做到这一点的建议吗?
把你的问题分解成更小的部分。您陷入困境的部分原因是因为您试图一次性制作整个解决方案,即使您正在尝试学习如何操作用于制作解决方案本身的工具。
这里有一个提示,我希望它可以帮助您解决问题,并且当您将来必须分解和分析类似问题时,您和其他初级脚本编写者将从中受益:
首先指定需要对每个文件执行的操作的确切性质。事实上,您应该能够手动编写处理从文件列表中采样的特定文件名所需的命令。 不做工作,只写命令。在您的示例中,每个文件都需要移动,是吗?因此,每个文件都需要一个mv
命令。而不是如何斗争做的mv
命令,只是担心如何创建它。您将如何手动编写一个这样的mv
命令来移动文件?那么问题就变成了如何获取awk
(或您想使用的任何工具)来输出该命令:
mv (filename) (to-where-you-want-it)
Run Code Online (Sandbox Code Playgroud)
对于您提供的每个文件名。当您学习新工具时,调试一个只创建一系列 shell 命令作为其输出的脚本,而不实际执行任何操作,这比调试一个只是横向移动并移动了数百个错误文件的脚本要容易得多进入数百个错误的目录,现在您不再确定任何东西在哪里。
对于初学者,请查阅man
您认为适合您的工具的页面。然后在手动模式下试验该命令,只是为了了解您需要做什么才能让该工具以您想要的方式解析您的输入并创建您需要的输出。在编写移动 100 或 1000 个文件的脚本之前,您需要一个只能正确移动一个文件的脚本。因此,创建一个测试用例,并花点时间与您认为可行的一个或多个工具“交朋友”。你的帖子被标记为awk,我认为这是一个明智的选择,所以让我们继续吧。
awk
有一个-F
参数,可用于指定分隔符,awk
用于将字符串分解为组件字段。该分隔符可以是一个简单的字符,也可以是括在方括号中的多个字符中的任何一个。在正则表达式中,这被称为字符类。您的输入同时使用连字符'-'
和句点'.'
作为字段分隔符,因此我们可以指定字符类[-.]
来告诉awk
在连字符或句点上进行拆分。请注意,awk
这并不关心哪个是哪个,并确保您的源目录不包含任何连字符或句点。
awk
打破每个文件名到分量场以 filename 为例A1-001.xyz
,尝试awk
手动通过此命令运行它,以了解awk
该文件名的作用:
$ awk -F[-.] '{print $0 " " $1 " " $2 " " $3}' <<< 'A1-001.xyz'
Run Code Online (Sandbox Code Playgroud)
该命令告诉awk
,“使用连字符和句点作为字段分隔符,打印整个输入行 ( $0
)、一个空格、字段 1、一个空格、字段 2、一个空格,最后是字段 3。
输出是:
A1-001.xyz A1 001 xyz
Run Code Online (Sandbox Code Playgroud)
希望这能告诉你很多:这$0
就是你在mv
命令源中需要的,因为这是完整的原始文件名;这$2
就是您在mv
命令目标中所需要的,因为这是您想要的数字目录名称。最大的实现是awk
可以mv
为您完全格式化命令,并打印出来。所需要的只是稍微调整一下awk
的print
语句。与其试图让您的脚本做所有事情,不如让脚本创建您需要执行的命令。这样,脚本中的错误不会使其崩溃并将文件移动到错误的位置。它只会打印一些错误的输出,您会注意到它是错误的,但不会造成任何伤害。
awk
命令的第二次迭代文件名前面可能有一个源路径。但请确保路径中没有任何.
或-
字符!因此mv
,每个文件的命令显然以mv
一个空格开头,然后是文件名(可能包括完整的源路径)、另一个空格以及您要将文件移动到的目录。为了更好地衡量,我们将在目标目录后放置一个斜杠。由于您没有更改文件名,我们将只指定目标目录并省略目标文件名。这样做也更容易,这是值得注意的。不要让事情变得比需要的更困难。
$ awk -F[-.] '{print "mv " $0 " " $2 "/"}' <<< '/path/to/directory1/A1-001.xyz'
mv /path/to/directory1/A1-001.xyz 001/
Run Code Online (Sandbox Code Playgroud)
查看print
命令:以mv
空格开头,然后$0
是完整的文件名;另一个空间,然后$2
这是输出子目录。同样,您必须确保源路径名称不包含任何连字符或句点,因为它们作为文件名中的字段分隔符具有特殊含义。更多的问题是,awk
不会正确拆分您的字段,并且您的脚本会中断。
但是目标目录不仅仅是$2
,它前面有一个前缀,就像源文件名一样。我们可以awk
为我们打印它,因为它每次都是一样的:
$ awk -F[-.] '{print "mv " $0 " /path/to/directory2/" $2 "/"}' <<< '/path/to/directory1/A1-001.xyz'
mv /path/to/directory1/A1-001.xyz /path/to/directory2/001/
Run Code Online (Sandbox Code Playgroud)
所以这看起来很有希望。现在制作一个文件列表file-list.txt
:
$ cat file-list.txt
A1-001.xyz
A29-002.xyz
A82-003.xyz
Run Code Online (Sandbox Code Playgroud)
然后awk
在整个文件列表上运行您的命令。请记住,这里没有什么害处,因为awk
所做的只是打印东西。它实际上并没有对移动文件做任何事情。它只是向您展示将执行您想要执行的操作的命令。
$ awk -F[-.] '{print "mv " $0 " /path/to/directory2/" $2 "/"}' < file-list.txt
mv A1-001.xyz /path/to/directory2/001/
mv A29-002.xyz /path/to/directory2/002/
mv A82-003.xyz /path/to/directory2/003/
Run Code Online (Sandbox Code Playgroud)
如果您有很多文件要移动,您需要将awk
上面的命令通过管道传输到其中,less
以便您可以仔细检查它。在错误的位置查找点和破折号,或者文件或目录名称中的其他奇怪字符。如果您愿意,可以将该输出的示例行复制并粘贴到 shell 提示符中,以确保它执行正确的操作。但这是一个足够简单的示例,我们可以通过检查进行测试。一旦您对这个mv
命令列表是您想要执行的操作感到满意,只需将 的输出awk
直接通过管道传输到其中sh
以执行它。如果您想在执行时查看命令,请使用sh -v
而不仅仅是sh
:
$ awk -F[-.] '{print "mv " $0 " /path/to/directory2/" $2 "/"}' < file-list.txt | sh -v
mv A1-001.xyz /path/to/directory2/001/
mv A29-002.xyz /path/to/directory2/002/
mv A82-003.xyz /path/to/directory2/003/
$
Run Code Online (Sandbox Code Playgroud)
我希望你不要反对这么详细的分解,但这类问题在 Stack Exchange 上经常出现,许多初学者认为他们的问题是一个独特的、一次性的问题,需要一个独特的解决方案。
脚本编写的真正关键是要意识到脚本编写提供了可以解决各种各样问题的通用工具,而精通的第一步就是学习如何用这些工具做小事,然后将这些小事组合成越来越大的东西。
第一步只是学习如何告诉awk
如何按照我们需要的方式分解文件名。每当您尝试从嵌入了多条信息的文件名中解析组件字段时,这都是一个关键步骤。
第二步是告诉 awk 自动打印命令中每个文件始终相同的部分(mv
开头,$2
字段前的目标路径),并将提取的文件名字段放在正确的位置. print
语句和它们的亲属是任何类型编码中最基本的部分之一,我不记得有多少伤害来自一个好地方print
陈述。可以肯定的是,您只想输出必要的内容,但是当您在学习并且不知道变量是什么时,打印它,询问很少会受到伤害。从长远来看,你会收回那个打印语句,但是“打印然后管道到外壳”的脚本技术的全部意义在于你有一个内置的“试运行”,因为你总是看起来在您实际将它们通过管道传输到 shell 以执行之前,在脚本输出的 shell 命令中。在复杂的情况下,即使在您的输出中添加评论也是公平的游戏,以“展示您的工作”:
$ awk -F[-.] '{print "# move file " $0 " to subdir " $2; print "mv " $0 " /path/to/directory2/" $2 "/"}' < file-list.txt
# move file A1-001.xyz to subdir 001
mv A1-001.xyz /path/to/directory2/001/
# move file A29-002.xyz to subdir 002
mv A29-002.xyz /path/to/directory2/002/
# move file A82-003.xyz to subdir 003
mv A82-003.xyz /path/to/directory2/003/
Run Code Online (Sandbox Code Playgroud)
第三个关键,可能与我的第二点密切相关,但我认为经常被忽视的一个是,当你在做一些对你来说有点牵强的事情时,不要写一个可能会出错的脚本然后离开您的文件都散落在无数不同但错误的地方。只需编写一个脚本,编写脚本来完成工作。以这种方式进行故障排除要容易得多。然后,当您最终获得正确的脚本时,只需将脚本输出(在您的示例中,一系列mv
命令,每个文件一个)通过管道传输到 shell,它们就会运行。