在Bash中按类型排序文件

时间:2014-04-17 15:48:19

标签: bash sorting find

我有一个包含多种文件的文件夹 如何通过文件扩展名对它们进行排序,留下一个名为“doc”的Word文档文件夹,一个带有jpgs的文件夹“jpg”等。

一些警告:

  • 这些文件目前位于不同深度的子目录中。没有必要保持相对路径 - 未分类/ 1995 / summer / DCIM中的文件应该直接进入sorted / jpg。

  • 两个文件可能具有相同的名称。在这种情况下,它必须忽略第二个文件[错误会很好],或者最好在移动时将某些内容附加到第二个文件的名称(file.jpg - > file_01.jpg)

  • 由于我不知道所有文件类型,如果不存在具有该名称的文件,脚本应该创建该文件夹。 (即如果它命中文件“old.wpd”,它应该创建一个文件夹sorted / wpd。

  • 我无法使用副本 - 我需要移动文件 - 因为我们正在处理大量文件,而且硬盘驱动器上没有空间来复制它们。

    < / LI>

我更喜欢Bash脚本,因为我对了解Bash有点兴趣,但如果需要可以使用Fish或ZSH。

为了我的学习,如果有一个可以保持相对路径的解决方案,我很想知道它是什么。

编辑:
我正在运行Mac,通过Brew安装coreutils 我失败的尝试使用了find,但我无法以可用的形式获得文件扩展名。

3 个答案:

答案 0 :(得分:2)

这是您可以使用的一个可能的实用功能。它需要一个基本目录和一个文件路径,并将该文件移动到基​​本目录的相应子目录中的唯一命名文件。

对于生产用途,我建议扩展该功能以获取文件名列表而不是单个文件名。

无论如何,您可以使用-exec命令的find选项来安排在所有适当的文件上调用该实用程序。如果您按照建议扩展功能,则需要使用-exec ... +而不是-exec ... {}来触发它。 (有关详细信息,请参阅man find。)

注意:我把它写成函数而不是脚本,但是find -exec不能调用bash函数。因此,您需要将函数包装在脚本文件中,或者将其解包到脚本文件中。

重要提示:另外,我输入了这个;我没有验证它是否有效。与往常一样,只有在存在良好备份的情况下以及在受控环境中进行仔细测试后,才能对文件系统进行大量更改。

# Usage: ext_move <directory> <file>
ext_move() {
  # Extract the filename from the path
  local base=$(basename $2)
  # Extract the (last) extension from the filename
  local ext=${base##*.}
  # Verify that it is really an extension
  # This test could be much more rigorous (eg. only alphanumerics)
  if [[ $ext = "$base" || $ext = "" ]]; then
    echo "'$2': No extension; not moved"
    return 1
  fi
  # Make sure the subdirectory exists
  if ! mkdir -p "$1/$ext"
    return 1
  fi
  # Try moving the file, but refuse to overwrite an existing file. If
  # this fails, then we need to find a different file name
  if ! mv -n "$2" "$1/$ext/$base" 2>/dev/null; then
    # Strip the extension off the base:
    base=${base%.$ext}
    # We don't try *too* hard here, because the move might fail for other reasons.
    local suf
    for suf in _{01..99}; do
      if mv -n "$2" "$1/$ext/$base$suf.$ext" 2>/dev/null; then
        return
      fi
    done
    # If we get here, we failed 100 different filenames. Maybe
    # there is some other problem. (filesystem full, permissions, etc.)
    # Repeat last move in order to present the error message
    mv -n "$2" "$1/$ext/$base$suf.$ext"
  fi
}

一些实施说明:

  1. 该函数旨在以原子方式工作,以防多个实例与不同的源文件并行执行,如果它是用xargs而不是{{1}触发的情况。 }。所以它需要确保对目标文件名存在的测试是原子的,这排除了执行列表-exec然后移动的事情。相反,我们只是尝试使用一种技术来进行移动,如果目标名称存在则该技术将失败。避免“修改前测试”竞争条件在脚本设计中始终很重要。

  2. test -f $name是一个Gnu扩展名,如果目标文件名存在,则导致移动失败。 Posix mv -n只会覆盖文件,这显然不是我们想要的。如果我们没有Gnu mv,我们可以通过使用mv将新名称链接到旧文件来实现相同的效果;如果新名称存在,这将失败,满足锁定要求,但在这种情况下,我们仍然需要在链接成功后实际执行此操作。虽然代码稍微复杂一些,但它有一些优点:第一,它更便携,第二,它允许更好地检测错误条件。因此,它更适合生产脚本。

答案 1 :(得分:1)

这是一个简短的假设您已安装GNU coreutils。

#!/bin/bash

destination=~/Test/pwetpwet

find "$1" -type f -execdir bash -c '
   base=${0#./}
   extension=${base##*.}
   [[ $extension != $base ]] || { echo >&2 "File $PWD/$base skipped: no extension"; exit 0; }
   destdir=$1/${extension,,}
   mkdir -p -- "$destdir" && mv --backup=numbered -- "$0" "$destdir"
' {} "$destination" \;

您可能希望回应“危险”行:

echo mkdir -p -- "$destdir" && echo mv --backup=numbered -- "$0" "$destdir"

用于测试目的。 --backup=numbered的{​​{1}}扩展名将创建编号备份,而不是覆盖文件。

此脚本只接受一个参数(源文件夹);你可以很容易地使它适应两个参数(源和目标)。

我没有彻底测试过,所以请自行承担风险!

答案 2 :(得分:0)

#!/bin/bash

destination=/path/to/destination/folder

find . -type f -depth -print0 |
while read -d '' -r filename; do
    base=$(basename "$filename")
    extension=${base ##*.}
    if [[ $base == $extension ]]; then
        echo "ignoring file with no dot in the name: $filename"
        continue
    fi

    # file.jpg and file.JPG should go to the same new folder
    ext_dir="$destination/$( tr '[:upper:]' '[:lower:]' <<< "$extension")"
    [[ -d "$ext_dir" ]] || mkdir "$ext_dir"

    if [[ -f "$ext_dir/$base" ]]; then
        # file.jpg already exists, find a new name
        base_noext=${base%.*}
        n=0
        while ((n++)); do
            printf -v base "%s_%03d.%s" "$base_noext" $n "$extension"
            [[ -f "$ext_dir/$base" ]] || break
        done
    fi

    if ln "$filename" "$ext_dir/$base"; then
        echo "successfully linked: $filename -> $ext_dir/$base"
        rm "$filename" || echo "could not remove: $filename"
    else
        echo "could not link: $filename -> $ext_dir/$base"
    fi
done

使用硬链接(ln)意味着您不必复制字节,因此假设您在同一文件系统中移动,这应该非常有效。

相关问题