我正在编写一个脚本来自动化我的照片管理,但我被困在了工作流程的最后一部分。脚本当前执行以下操作。。。
- 查询给定的文件夹,以使用其文件扩展名查找任何图像
- 一旦找到一个文件,它就会查询EXIF DateTime字段
- 使用DateTime字段,将在另一个位置使用以下语法创建文件夹结构/2016年10月10日/2016年10月31日/
- 然后将照片移动到与其DateTime匹配的新文件夹中/2016年10月10日至2016年3月31日/random_photo793958.jpg
- 该照片随后被重命名为/2016/10年10月10日.2016/10.1.2016_01.jpg
如果任何一天只存在一张照片,这很好,但它只是覆盖当前状态下的现有照片。我不知道如何构建一个循环来检查文件是否存在,并将_01更改为_02等等
我试过使用While循环,但总是会陷入无限循环。使用我目前的结构,做这件事的最佳循环是什么?
dateTime=`identify -verbose "$2" | grep "exif:DateTime:" | awk -F' ' '{print $2" "$3}'`
eDate=`echo $dateTime | awk -F' ' '{print $1}'`
year=`echo $eDate | awk -F ":" '{print $1}'`
monthNum=`echo $eDate | awk -F ":" '{print $2}'`
monthString=`echo $eDate | awk -F ":" '{print $2}' | sed -e 's/01/01.janurary/' | sed -e 's/02/02.feburary/' | sed -e 's/03/03.march/' | sed -e 's/04/04.april/' | sed -e 's/05/05.may/' | sed -e 's/06/06.june/' | sed -e 's/07/07.july/' | sed -e 's/08/08.august/' | sed -e 's/09/09.september/' | sed -e 's/10/10.october/' | sed -e 's/11/11.november/' | sed -e 's/12/12.december/'`
day=`echo $eDate | awk -F ":" '{print $3}'`
oldPhotoName=$(echo $2 | awk -F"/" '{print $NF}') #strip off last portion of $2 to get the photo name into a variable
fileExt=$(echo $2 | awk -F"." '{print $NF}' | sed 's/./L&/g') #create variable with the file extension and convert it to lowercase
mkdir -p "$outputDir/$year/$monthString/$monthNum.$day.$year" && mv -f "$2" "$outputDir/$year/$monthString/$monthNum.$day.$year/"
n=01
mv -f "$outputDir/$year/$monthString/$monthNum.$day.$year/$oldPhotoName" ""$outputDir/$year/$monthString/$monthNum.$day.$year/$monthNum.$day.$year"_$n.$fileExt"
您已经标记了您的问题bash。虽然您可以使用grep
、awk
、cut
等实用程序来解析所需的文本,但请注意,对实用程序的每次调用都会在新的子shell中产生自己的进程。如果你在数千个文件上循环,每个迭代产生10个子shell,那么这可能会相加。
不需要进行大部分调用,因为bash
提供了自己的文本操作例程(例如,参数扩展,带有子字符串删除和替换、字符串索引等),可以大大加快速度。您在POSIX shell中有相同的参数扩展,但没有字符串索引。
下面是一个如何处理bash
内建内容的示例,并向您展示了在发生文件冲突时如何处理递增的_01, _02, ..
。命名还考虑了那些暗示你倾向于逻辑排序的命名约定的评论。exif
标签提供了一种易于解析的YYYYYMMDD_HHMMSS
日期/时间格式(下面我将向您展示如何分解为各个组件)。你们可以用任何你们喜欢的方式(这是你们的),但一系列的评论会导致如下结果:
按年份列出的目录,包含每月的子目录01-12
,然后是带有完整日期戳的单个文件名,例如
YYYY/
+- 01/
| +- YYYYMMDD_HHMMSS.jpg # duplicate timestamped files
| +- YYYYMMDD_HHMMSS_01.jpg
| +- YYYYMMDD_HHMMSS_02.jpg
|
+- 02/
|
...
|
+- 12/
文件名示例为:
/home/david/tmp/2016/10/20161010_163345.jpg
如果遇到在同一秒拍摄的照片,则它们将被移动到与相同的位置
20161010_163345_01.jpg
20161010_163345_02.jpg
...
使用参数扩展和子字符串移除是直接的。substring
必须从字符串的末尾到字符串内的某个点匹配(如下图所示,从左或右)。允许使用通配符、球符:
${string#substring} # remove 1st occurence of substring from left
${string%substring} # remove 1st occurence of substring from right
${string##substring} # remove up to last occurence starting from left
${string%%substring} # remove up to last occurence starting from right
字符串索引也是直接的
${string:position:len} # extract 'len' number of chars beginning at position
(您可以通过使position
为负数来设置字符串末尾的位置,,但您必须(1)在:
和数字之间留一个空格(例如${foo: -2:1}
)或在括号中加一个负数position
(如${foo:(-2):1}
))
考虑到这一点,您可以相对容易地使日期/时间戳的每个组件可用于命名约定,并处理path
和extension
的拆分。我已经努力评论下面的代码,以帮助您跟随:
#!/bin/bash
fullfn="$1" # the full filename including: /path/to/your/image.jpg
# exif datetime (original),
# remove ' Value: ' label,
# remove all :, translate ' ' to _
datetime=$(exif -t 0x9003 "$fullfn" |
sed -n 's/^.*Value:[ ]//p' |
tr -d ':' | tr ' ' _)
# validate $datetime is YYYYMMDD_HHMMSS
[[ $datetime =~ [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9][0-9][0-9][0-9][0-9][0-9] ]] || {
printf "error: invalid date/time from '%s'n" "$fullfn"
exit 1
}
dtdate=${datetime%_*} # isolate YYYYMMDD
dttime=${datetime#*_} # isolate HHMMSS
dtyear=${dtdate:0:4} # split YYYYMMDD into YYYY
dtmon=${dtdate:4:2} # into MM
dtday=${dtdate:6:2} # into DD
path="${fullfn%/*}" # isolate path from /path/fname.ext
fn="${fullfn#"$path"/}" # isolate fn from path
test "$fullfn" = "$path" && path='.' # if no path, set to '.' (or $PWD)
ext="${fn##*.}" # isolate ext from fn.ext
test "$ext" = "$fn" && ext= # unset if no extension present
if test -n "$ext"
then
lcext=".${ext,,}" # I like my extensions lowercase
else
lcext=".jpg" # if no extension, then its 'jpg' (or leave blank)
fi
## create your directories as desired here
# e.g.
newpath=~/tmp/$dtyear/$dtmon ## SUBSTITUTE YOUR LOCATION FOR tmp HERE
mkdir -p "$newpath"
# test no duplicate before moving to new directory
declare -i n=1
newfn="${datetime}$lcext" # YYYYMMDD_HHMMSS.jpg
newname="$newfn" # copy of $newfn to update if req'd
while test -f "$newpath/$newname" # increment 'n' until no conflict
do
printf -v newname "%s_%02d%s" "${newfn%$lcext}" "$n" "$lcext"
((n++))
done
newfn="$newname"
printf "mv %snto %sn" "$fullfn" "$newpath/$newfn"
mv "$fullfn" "newpath/$newfn"
exit 0
注意:这只是一个使用单个文件名并根据需要对其进行操作的示例。您需要在循环中包含类似的内容,以处理您想要排序和移动的所有文件。还要注意,exif
采用tag
选择-t
。上面的-t 0x9003
对应于图像的Date and Time (Original)
。可以使用exif -l imagename.jpg
检查图像的所有可用标记。
示例使用/输出
$ ./splitexif.sh ~/tmp/100_4423.JPG
mv /home/david/tmp/100_4423.JPG
to /home/david/tmp/2016/10/20161010_163345.jpg
$ cp ~/tmp/100_4423.JPG foo
$ ./splitexif.sh ~/tmp/foo
mv /home/david/tmp/foo
to /home/david/tmp/2016/10/20161010_163345_01.jpg
如果再打电话,你会得到:
$ ./splitexif.sh ~/tmp/foo
mv /home/david/tmp/foo
to /home/david/tmp/2016/10/20161010_163345_02.jpg
仔细看看,如果你有任何问题,请告诉我。
您可以使用一个属性(或属性组合)来使文件唯一(或者至少在给定目标的情况下足够可能是唯一的),例如文件大小(例如字节)或校验和,并将其附加在目标目录中的文件名末尾。
这样做的好处是,如果您实际复制同一个文件两次,最终不会得到两个副本,而是用另一个完全相同的副本覆盖旧文件。
例如:
#(assuming variable `file` contains the full path of the file)
#(assuming variable `dir` contains the target directory to move file to)
name="$(basename "$file")"
md5="$(md5sum "$file" | cut -f1 -d" ")"
target_name="${md5}_$name")
mv "$file" "$dir/$target_name"
我简化了扩展处理,将MD5校验和放在该示例的开头,您可能更喜欢其他方式。
请注意,这避免了对循环的任何需要,并确保对于任何一组文件,无论文件的处理顺序如何,最终结果都是相同的。