根据元文件的内容将文件高效地排序到子目录中

根据元文件的内容将文件高效地排序到子目录中

—编辑:更多细节,正确代码—

我想通过元数据文件中的数据将文件从目录移动到子目录。

有几组文件,例如<name>.<extension>。每组由 3 个文件组成。每组中有一个文件扩展名为.idx。这是元数据文件,即文本文件。

在元数据文件中,只有一行类似VALUE_01=XXXXX,还有一些其他行(15 到 60 行,<key>=<value><key>唯一的)。

现在我想将所有文件从当前目录移动到此例中名为 (键的值)<name>.*的子目录。XXXXXVALUE_01

我尝试了for循环等,但即使是 a 也ls *.idx不起作用,因为有循环200 万个文件好了!所以它不起作用,我需要一些性能。

我试过

find . -maxdepth 1 -type f -name "*.idx" -exec grep -H "VALUE_01=" {} ";" | perl -pe 's/(.*?).idx:VALUE_01=(.*)$/\1.* .\/\2\//'

所以我得到了一个像这样的列表

./文件1.* ./XXXXX/
./文件2.* ./XXXXX/ ./
文件3.* ./YYYYY/

我试图将其作为参数传递xargsmv

... | xargs mv

要得到

mv ./<name>.* ./XXXXX/

但我收到错误消息

mv: 无法统计‘./XXXXX/’:没有此文件或目录
mv: 无法统计‘./file1.':没有此文件或目录
mv:无法统计'。/XXXXX/':没有此文件或目录
mv:无法统计'。/file2。
':没有这样的文件或目录
mv:警告:源目录'。/YYYYY/'指定多次
mv:无法统计'。/file3。*':没有这样的文件或目录

我认为,这是不正确的用法xargs

我不擅长 shell 编程,所以我不知道如何使用它,或者如何避免它。

答案1

假设您想将所有匹配的文件移动到从同一子目录中的相应文件file.*检索的子目录中,那么像这样的结构可能就是您要寻找的(留下用于测试):VALUE_01file.idxecho

find . -name '*.idx' -execdir sh -c '
  for idx do
    tgt=$(grep -m1 -Po "VALUE_01=\K.*" "$idx")
    [ -n "$tgt" ] && echo mkdir -p "$tgt" || continue
    printf "%s\0" "${idx%.idx}".* | xargs -r0 echo mv -nt "$tgt" --
  done
' sh {} +

进行简短测试

==> ./dir/file.idx <==
VALUE_01=XXXXX

==> ./dir/foo.idx <==
VALUE_01=YYYYY

给出

$ find . -name '*.idx' -execdir sh -c '
  for idx do
    tgt=$(grep -m1 -Po "VALUE_01=\K.*" "$idx")
    [ -n "$tgt" ] && echo mkdir -p "$tgt" || continue
    printf "%s\0" "${idx%.idx}".* | xargs -r0 echo mv -nt "$tgt" --
  done
' sh {} +
mkdir -p XXXXX
mv -nt XXXXX -- ./file.idx ./file.json
mkdir -p YYYYY
mv -nt YYYYY -- ./foo.awk ./foo.idx ./foo.py

结束--选项列表的 并不是绝对必要的,因为-execdir会添加前置./,但我喜欢它作为视觉提示。-m1中的grep只是让它在找到匹配项后立即退出,这可能.idx如果文件有大量以下内容,则会产生影响。如果您知道目标目录已经存在,则可以省略mkdir。或者,您可以测试是否存在。

答案2

诀窍是使用for file in *idx。无论如何你都不想这样做for file in $(ls *idx),请参阅 Bash 陷阱 #1,而且无论如何,shell 无法处理扩展这么多文件名,正如你已经看到的。但是,使用内置命令可以for解决这个问题,因此你可以尝试这样的操作:

for file in *.idx; do
  name="${file%%.idx}"
  num=$(grep -m 1 -oP 'VALUE_01=\K\S+' "$file")
  mkdir -p "$num"
  printf 'mv %s* %s/' "$name" "$num"
done > script.sh

解释

  • for file in *.idx; do ... done:遍历所有名称以 结尾的文件和目录.idx,将每个文件和目录保存为$file
  • name="${file%%.idx}:此语法将返回从变量右侧移除的模式最长匹配项${var%%pattern}的值。请参阅$varpatternhttps://tldp.org/LDP/abs/html/string-manipulation.htmlfile. 因此如果给定的话这将返回file.idx
  • num=$(grep -m 1 -oP 'VALUE_01=\K\S+' "$file"):从 idx 文件中获取目标目录的名称。 指示-mgrep第一次匹配后停止搜索,因为没有必要处理整个文件。接下来, 表示-o“仅打印行的匹配部分”,并且-P启用聚合酶链反应这给出了\K“忽略到目前为止匹配的所有内容”和\S+一个或多个非空白字符”的表达式。因此,它将查找字符串VALUE_01=,然后打印它在其后找到的最长的非空白字符串。请注意,这假设您的字符串中XXXX没有空格。
  • mkdir -p "$num":如果目标目录尚不存在,则创建它。您需要这样做,-p因为mkdir如果目录已存在,则它什么也不做。
  • printf 'mv %s* %s/' "$name" "$num":打印出需要运行的命令(例如mv foo* XXXX)。
  • ... done > script.sh:将上一步打印的所有命令捕获到一个名为的文件中script.sh

这将使您能够检查命令并手动尝试一些命令以查看它们是否有效。如果有效,您可以直接运行sh script.sh以执行它们,或者重新执行循环,但这次执行命令而不是打印它们:

for file in *.idx; do
  name="${file%%.idx}"
  num=$(grep -m 1 -oP 'VALUE_01=\K\S+' "$file")
  mkdir -p "$num"
  mv "$name"* "$num"
done > script.sh

相关内容