【问题标题】:Sorting 271,568 Files in Bash Based on File Names根据文件名对 Bash 中的 271,568 个文件进行排序
【发布时间】:2016-04-08 18:17:43
【问题描述】:

我有 271,568 个需要排序的文件的集合,它们都在同一个目录中。幸运的是,它们都可以根据它们所在的文件夹方便地命名。

例如,一小部分文件可能如下所示:

.
├── file.sort.shamwow
├── file.sort.shamwow.abc
├── file.sort.shamwow.example.alsoafile
├── file.sort.shamwow.example.file
├── foo.bar
├── foo.bar.a
├── foo.bar.b
├── foo.lel
├── foo.wow.a.50
└── foo.wow.b

当它们完成排序后,它们应该如下所示:

.
├── file
│   └── sort
│       └── shamwow
│           ├── example
│           │   ├── file.sort.shamwow.example.alsoafile
│           │   └── file.sort.shamwow.example.file
│           ├── file.sort.shamwow
│           └── file.sort.shamwow.abc
└── foo
    ├── bar
    │   ├── foo.bar
    │   ├── foo.bar.a
    │   └── foo.bar.b
    ├── foo.lel
    └── wow
        ├── foo.wow.a.50
        └── foo.wow.b

因此,文件 foo.wow.a.50 将被放置在目录 wow 内,即目录 foo 内,以此类推所有文件。

我想要的程序会根据点在目录中的位置对文件进行排序。但是,如果该文件夹中只有一个文件(例如 foo/wow/a.50),则不会仅为该文件创建新文件夹。

现在,我的半功能脚本如下所示:

#!/bin/bash
#organization for gigantic folder

> foo.txt

for f in *; do
    d=${f:3}
    d=${d%%.*}
    d=${d%%.*}
    echo $d

    if grep -Fxq "$d" foo.txt
    then
        mkdir -p $d
        mv $f $d
    else
        echo $d >> foo.txt
    fi
done

rm foo.txt

但它并没有那么好。

有人可以修复我的代码,或者自己动手整理一下这个烂摊子吗?谢谢!

【问题讨论】:

  • 我建议您将ls 引用从您的问题中取出——它往往会为您提供一堆指向mywiki.wooledge.org/ParsingLs 的链接,而您的问题实际上不需要任何使用ls 在所有。
  • @CharlesDuffy 感谢您的建议。我将其更改为“根据文件名对 Bash 中的 271,568 个文件进行排序”。
  • BTW,不能有三个文件分别命名为foo/barfoo/bar/afoo/bar/b,后两者的目录名会和文件名冲突对于前者。
  • 我和达菲在一起;至少您需要知道如何处理 foo.bar 的情况: foo/ 中的一个名为 foo.bar 的文件? foo/bar/ 中的文件也称为 bar?跳过它?
  • 那么你想要的输出不应该是foo/wow/a.50foo/wow/b而不是foo/wow.a.50foo/wow.b吗?

标签: linux bash file sorting filesystems


【解决方案1】:

忽略您请求的输出无法在文件系统上表示(需要相同的名称来引用文件和目录):

#!/bin/bash
#      ^^^^- must be bash shebang, must be shell 4.0 or newer

# first pass: count directory references
declare -A refcounts=( )
for f in *; do
  f_part=$f
  while [[ $f_part = *.* ]]; do
    refcounts[$f_part]=$(( ${refcounts[$f_part]} + 1 ))
    f_part=${f_part%.*}
  done
  refcounts[$f_part]=$(( ${refcounts[$f_part]} + 1 ))
done

# second pass: use that information
# ...this is some ugly code, but I don't have the time right now to make it simpler.
for f in *; do
  f_part=${f%%.*}
  f_rest=${f#*.}
  while : "f=$f; f_part=$f_part; f_rest=$f_rest"; do
    new_piece=${f_rest%%.*}
    [[ $new_piece ]] || break
    f_part_next=${f_part}.$new_piece
    f_rest_next=${f_rest#"$new_piece"}; f_rest_next=${f_rest_next#.}
    if [[ $f_rest = *.* ]] && (( ${refcounts[${f_part_next}]:-0} > 1 )); then
      f_part=$f_part_next
      f_rest=$f_rest_next
    else
      break
    fi
  done
  dest="${f_part//"."/"/"}/${f_rest}"
  mkdir -p -- "${dest%/*}"
  mv -- "$f" "$dest"
done

【讨论】:

  • 谢谢。我明天试试这个。
  • 有很多 mkdir 和 mv 错误。它成功了一半。我认为我认为我误解的一个问题是程序不需要重命名文件。这可能会让它发挥作用。
  • 对您问题的修改使这个答案完全无效。正如开头段落所说,它试图做你最初要求的不可能的事情——所以 当然 它预计会失败。 :)
  • ...也就是说,它的一般要点——两遍设计,在第一遍中建立一个引用计数表并在第二遍中执行文件系统操作——是正确的。要使这项工作适用于您的新问题和不同的问题,您需要进行的唯一更改将是在第二遍中 - 新代码应该比它替换的代码要简单得多。
  • 谢谢查尔斯。我会努力做到的。
猜你喜欢
  • 2021-01-23
  • 1970-01-01
  • 2020-04-01
  • 2016-02-27
  • 1970-01-01
  • 1970-01-01
  • 2017-08-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多