find, xargs:为每个文件执行命令链

find, xargs: execute chain of commands for each file

如果问题标题信息量不够,我很抱歉。请随时提出更好的变体。

我要执行以下任务: 在一个目录中,我有许多 JPEG 格式的照片文件。我想从 EXIF 中提取这些照片的拍摄日期,为每个日期创建一个新目录,并将文件移动到相关目录。

(EXIF 日期和时间的格式为 YYYY:MM:DD hh:mm:ss,我希望目录名称的格式为 YYYY-MM-DD,这就是我使用 sed 的原因)

我有点知道如何分别执行这些任务,但无法将它们组合在一起。我花了一些时间研究如何使用 find-execxargs 来执行命令,但仍然无法理解如何正确链接所有内容。

最后我能够使用两个命令完成我的任务:

find . -name '*.jpg' -exec sh -c "identify -format %[exif:DateTimeOriginal] {}
    | sed 's/ [0-9:]*//; s/:/-/g' | xargs mkdir -p" \;

find . -name '*.jpg' -exec sh -c "identify -format %[exif:DateTimeOriginal] {}
    | sed 's/ [0-9:]*//; s/:/-/g; s/$/\//' | xargs mv {}" \;

但是我不喜欢重复,也不喜欢-exec sh -c。有没有正确的方法可以在不使用 -exec sh -c 的情况下在一行中执行此操作?

与其专注于单行代码,更好的解决方案是将逻辑放入脚本中,以便于执行和测试。将其放入名为 movetodate.sh:

的文件中
#!/usr/bin/env bash

# This script takes one or more image file paths

set -e
set -o pipefail

for path in "$@"; do
    date=$(identify -format %[exif:DateTimeOriginal] | sed 's/ [0-9:]*//; s/:/-/g')
    dest=$(dirname "$path")/$date
    mkdir -p "$dest"
    mv "$path" "$dest"
done

然后,调用它:

find . -name '*.jpg' -exec ./movetodate.sh {} +

exiftool很容易做到:

exiftool "-Directory<DateTimeOriginal" -d %Y-%m-%d *.jpg

例如,该命令将这样的布局转换为:

.
├── a.jpg  (2013:10:17 10:01:00)
└── b.jpg  (2012:08:07 16:11:15)

对此:

.
├── 2012-08-07
│   └── b.jpg
└── 2013-10-17
    └── a.jpg

如果你还想使用identify,命令可以改写如下:

script=$(cat <<'SCRIPT'
d=$(
  d=$(identify -format "%[exif:DateTimeOriginal]" "[=13=]" 2>/dev/null) || exit $?
  d=${d:0:10}
  printf '%s/%s' "$(dirname "[=13=]")" "${d//:/-}"
) || exit $?
mkdir -p "$d" && mv -v "[=13=]" "$d"
SCRIPT
)

find "$dir" -name '*.jpg' -exec bash -c "$script" {} \;

请注意脚本中 [=15=] 变量的使用。我们将 {} 占位符作为第一个参数传递给脚本。

for file in "$@" 循环的帮助下,脚本可以很容易地转换为接受多个参数(路径)。在这种情况下,\; 字符应替换为 +。但是,如果您有大量超过 $(getconf ARG_MAX) 限制的文件,您将需要 xargs,或者如上面的脚本所示一个一个地处理文件。同样的注意事项也适用于 exiftool 命令。

使用 parallel 你不需要脚本而是做:

doit() {
  path=""
  date=$(identify -format %[exif:DateTimeOriginal] | sed 's/ [0-9:]*//; s/:/-/g')
  dest=$(dirname "$path")/$date
  mkdir -p "$dest"
  mv "$path" "$dest"
}
export -f doit
find . -name '*.jpg' | parallel doit