使用变量处理目录中的文件

使用变量处理目录中的文件

该脚本将在 Ubuntu 22.04.1 LTS 上运行

我对 Ubuntu 和脚本编写还不熟悉,但我曾用其他操作系统(主要是 VMS)和 C(很多年前)编写过代码和操作系统脚本。我知道 Linux 经常将多个命令放在一行上,但我想尝试保留代码,以便以后更容易准备。因此,每行单个命令是首选。

我正在尝试循环遍历目录中两个文件名之间的所有文件,并且我希望代码具有灵活性,以便我可以随时间进行修改。例如,我希望能够处理首字母介于 D* 和 J* 之间的所有文件名。文件名确实包含空格和其他特殊字符。

我想将源根目录和目标根目录作为变量传入,并在循环内访问这些变量。我还想计算成功处理的文件数和失败的文件数,以便在执行结束时显示一次。我可以看到计数在循环内增加,但值在循环外不存在。

我的开始还不错。我可以:* 循环查找文件(但不受起始/结束字母的限制)* 计算命令执行的时间,我计划稍后将其添加到日志文件中* 计算成功和失败的次数,但循环后无法显示。

我有三个问题:

  1. 变量 SouceDirectoryRoot 和 DesinationDirectoryRoot 在循环中无法通过 find 命令访问。我想在循环中使用它们,这样我就可以在 DestinationDirectory 中根据需要创建子目录。我不想设置它们两次,一次在循环内,一次在循环外。我的长期目标是将它们作为参数传递给脚本,而不是像现在这样硬编码。

  2. 与问题 1 类似,在 find 命令的循环之后,cntSuccess 和 cntFail 的值不可用。我可以看到它们在循环内正确递增,但在循环之后不存在。我希望在最后有一个显示成功和失败次数的输出。我现在有输出,但值为零。

  3. 我不知道如何限制文件,使它们介于变量 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来更有效地处理文件。

我希望这可以给你一些想法。

相关内容