该脚本将在 Ubuntu 22.04.1 LTS 上运行
我对 Ubuntu 和脚本编写还不熟悉,但我曾用其他操作系统(主要是 VMS)和 C(很多年前)编写过代码和操作系统脚本。我知道 Linux 经常将多个命令放在一行上,但我想尝试保留代码,以便以后更容易准备。因此,每行单个命令是首选。
我正在尝试循环遍历目录中两个文件名之间的所有文件,并且我希望代码具有灵活性,以便我可以随时间进行修改。例如,我希望能够处理首字母介于 D* 和 J* 之间的所有文件名。文件名确实包含空格和其他特殊字符。
我想将源根目录和目标根目录作为变量传入,并在循环内访问这些变量。我还想计算成功处理的文件数和失败的文件数,以便在执行结束时显示一次。我可以看到计数在循环内增加,但值在循环外不存在。
我的开始还不错。我可以:* 循环查找文件(但不受起始/结束字母的限制)* 计算命令执行的时间,我计划稍后将其添加到日志文件中* 计算成功和失败的次数,但循环后无法显示。
我有三个问题:
变量 SouceDirectoryRoot 和 DesinationDirectoryRoot 在循环中无法通过 find 命令访问。我想在循环中使用它们,这样我就可以在 DestinationDirectory 中根据需要创建子目录。我不想设置它们两次,一次在循环内,一次在循环外。我的长期目标是将它们作为参数传递给脚本,而不是像现在这样硬编码。
与问题 1 类似,在 find 命令的循环之后,cntSuccess 和 cntFail 的值不可用。我可以看到它们在循环内正确递增,但在循环之后不存在。我希望在最后有一个显示成功和失败次数的输出。我现在有输出,但值为零。
我不知道如何限制文件,使它们介于变量 StartFile 和 EndFile 名称之间。目录树(包括子目录)有数百个文件,转换命令(此处未包含)每个文件可能需要 +30 分钟。因此,我想同时运行此脚本的多个版本(或稍后将其转换为传递的参数而不是硬编码值),每个版本处理不同的文件子集。
SourceDirectoryRoot=/mnt/media_bulk/movies
DesitationDirectoryRoot=/mnt/media_bulkd/movies-H265
StartFile=D*
EndFile=J*
cntSuccess=0
cntFail=0
find $SourceDirectoryRoot -type f -exec sh -c '
for FileSpec do
echo ""
echo "File spec: $FileSpec"
FileName=${FileSpec##*/}
# echo "File name: $FileName"
echo "Source $SourceDirectoryRoot"
StartTime=$(date +%F" "%T)
echo "Start time: $StartTime"
StartSeconds=$(date -d "${StartTime}" +%s)
#command to time duration goes here
#
# save the status of the command so it can be used later
status=$?
if [ $status -eq 0 ]
then
# command was successful
echo "The command was succesful"
else
# the command had an error
echo "The command failed"
fi
EndTime=$(date +%F" "%T)
echo "End time: $EndTime"
EndSeconds=$(date -d "${EndTime}" +%s)
DurationSeconds="$(($EndSeconds-$StartSeconds))"
Duration=$(date -d @${DurationSeconds} +"%H:%M:%S" -u)
echo "Duration: $Duration"
if [ $status -eq 0 ]
then
# command was successful
echo "The command was successful and executed for $Duration"
cntSuccess=$(($cntSuccess+1))
echo "cntSuccess = $cntSuccess"
else
# the command had an error
echo "The command failed after $Duration"
((++cntFail))
fi
done' sh {} + #end for loop
echo "$cntSuccess files successfully processed"
echo "$cntFail file failed to process"
输出子集(我将因变量无法访问而导致的问题以粗体显示):
File spec: /mnt/media_bulk/movies/Marvel/Captain America 2 (9).m4v
Source
Start time: 2022-12-27 14:33:22
The command was successful
End time: 2022-12-27 14:33:22
Duration: 00:00:00
The command was successful and executed for 00:00:00
cntSuccess = 275
File spec: /mnt/media_bulk/movies/Marvel/The Avengers 2 (11).m4v
Source
Start time: 2022-12-27 14:33:22
The command was successful
End time: 2022-12-27 14:33:22
Duration: 00:00:00
The command was successful and executed for 00:00:00
cntSuccess = 276
0 files successfully processed
0 file failed to process
答案1
该范例find -exec sh -c '...' {} +
通常是处理文件的一种好方法 - 尽管对于多行内容,我会考虑将处理循环移动到单独的 shell 脚本并以 身份执行find -exec /path/to/script {} +
。
-name
您可以使用glob 模式或正则表达式来限制文件的范围-regex
- 例如,-name '[D-J]*'
仅匹配名称在您的语言环境中的词汇范围内排序的D
文件J
。
您可以传递变量值进入子 shell 进程出口它们来自父环境(export SourceDirectoryRoot
)。不幸的是,(据我所知)没有等效机制将值传递给父级。您可以考虑将它们写入状态或日志文件,然后再读取它们(如果作业被终止或中断,这也可以提供一些持久性)。
或者,您可以重构代码以在父 bash shell 中执行所有处理,并使用以下方式将文件名列表作为空分隔列表传递:流程替代:
#!/bin/bash
SourceDirectoryRoot=/mnt/media_bulk/movies
cntSuccess=0
while IFS= read -r -d '' file; do
printf 'processing file: %s\n' "$file"
((cntSuccess++))
done < <(find "$SourceDirectoryRoot" -type f -name '[D-J]*' -print0)
printf '%d files successfully processed\n' "$cntSuccess"
您可能还想考虑使用 GNUparallel
来更有效地处理文件。
我希望这可以给你一些想法。