循环遍历文件名列表并迭代 variable/array 从文件名中删除所有字符串 bash

Loop thru a filename list and iterate thru a variable/array removing all strings from filenames with bash

我在变量中有一个字符串列表,想从文件名列表中删除这些字符串。我正在从一个文件中提取该字符串,我可以随着时间的推移添加和修改该文件。变量中的某些字符串可能包含需要删除的项目的一部分,而其他字符串可能是列表中的另一行。这就是为什么我需要遍历整个变量列表的原因。

我熟悉使用 while 循环遍历列表,但不确定如何遍历每一行以从该文件名中删除所有字符串。

这是一个例子:

getstringstoremove=$(cat /text/from/some/file.txt)
echo "$getstringstoremove"

# Or the above can be an array
getstringstoremove=$(cat /text/from/some/file.txt)
declare -a arr=($getstringstoremove)

以上2行应该return以下行

-SOMe.fil
(Ena)M-3_1
.So[Me].filEna)M-3_2
SOMe.fil(Ena)M-3_3

这是我的循环 运行 从目录中获取所有文件名并删除文件名以外的任何内容

ls -l "/files/in/a/folder/" | awk -v N=9 '{sep=""; for (i=N; i<=NF; i++) {printf("%s%s",sep,$i); sep=OFS}; printf("\n")}' | while read line; do 
echo "$line"

return每次循环后结果如下

# 1st loop 
ilikecoffee1-SOMe.fil(Ena)M-3_1.jpg
# iterate thru $getstringstoremove to remove all strings from the above file.
# 2nd loop
ilikecoffee2.So[Me].filEna)M-3_2.jpg
# iterate thru $getstringstoremove again
# 3rd loop
ilikecoffee3SOMe.fil(Ena)M-3_3.jpg
# iterate thru $getstringstoremove and again
done

最终期望的输出如下

ilikecoffee1.jpg
ilikecoffee2.jpg
ilikecoffee3.jpg

我是 运行 在 bash 上 Mac。 我希望这是有道理的,因为我被困住了并且可以使用一些帮助。

如果有人有更好的方法来做到这一点,那不一定是我上面列出的方法。

我想我已经理解你的意思了,我会用标准内置的 Perl 来完成 macOS - 所以不需要安装。

我假设您有一个名为 remove.txt 的文件,其中包含要删除的内容列表,并且您想要 运行 当前目录中所有文件的脚本。如果是这样,脚本将是:

#!/usr/local/bin/perl -w
use strict;

# Load the strings to remove into array "strings"
my @strings = `cat remove.txt`;
for(my $i=0;$i<$#strings;$i++){
   # Strip carriage returns and quote metacharacters - e.g. *()[] 
   chomp($strings[$i]);
   $strings[$i] = quotemeta($strings[$i]);
}

# Iterate over all filenames
my @files = glob('*');
foreach my $file (@files){
   my $new = $file;
   # Iterate over replacements
   foreach my $string (@strings){
      $new =~ s/$string//;
   }
   # Check if name would change
   if($new ne $file){
      if( -f $new){
         printf("Cowardly refusing to rename %s as %s since it involves overwriting\n",$file,$new);
      } else {
         printf("Rename %s as %s\n",$file,$new);
         # rename $file,$new;
      }
   }
}

然后将其保存在您的 HOME 目录中作为 renamer。使其可执行 - 只需要一次 - 在终端中使用此命令:

chmod +x $HOME/renamer

然后你可以进入你疯狂命名的文件所在的任何目录,运行脚本如下:

cd path/to/mad/files
$HOME/renamer

对于您从 Internet 下载的所有内容,请先进行备份,然后只 运行 复制一小部分文件,直到您了解它是如何工作的。

如果你使用 homebrew 作为你的包管理器,你可以安装 rename 使用:

brew install rename

然后你可以从我的另一个答案中提取所有的 Perl 并将其压缩成几行并将其嵌入到 rename 命令中,这会给你带来额外的好处,即能够干 -运行等。下面的代码与我的其他答案完全相同,但对于 non_perl 人来说有点难以阅读。

你的命令很简单:

rename --dry-run '
   my @strings = map { s/\r|\n//g; $_=quotemeta($_) } `cat remove.txt`;
   foreach my $string (@strings){ s/$string//; } ' *

示例输出

'ilikecoffee(Ena)M-3_1' would be renamed to 'ilikecoffee'
'ilikecoffee-SOMe.fil' would be renamed to 'ilikecoffee'
'ilikecoffee.So[Me].filEna)M-3_2' would be renamed to 'ilikecoffee'

要尝试理解它,请记住:

  • rename 部分将以下 Perl 应用于每个文件,因为末尾有星号
  • @strings 部分从文件 remove.txt 中读取所有字符串并从中删除任何回车符 returns 和换行符并引用任何元字符
  • foreach 将每个删除应用到 rename 为您存储在 $_ 中的当前文件名

请注意,此方法在某种程度上以简单性换取了性能。如果你有数百万个文件要做,另一种方法会更快,因为在这里我为每个检查名称的文件读取 remove.txt 文件,但如果你只有几个 hundred/thousand 文件,我怀疑你会注意到它。


这应该大同小异,只是更短:

rename --dry-run '
   my @strings = `cat remove.txt`; chomp @strings;
   foreach my $string (@strings){ s/\Q$string\E//; } ' *

您可以使用此 awk 单行代码获取新文件名:

$ awk 'NR==FNR{a[[=10=]];next} {for(i in a){n=index([=10=],i);if(n){[=10=]=substr([=10=],0,n-1)substr([=10=],n+length(i))}}} 1' rem.txt files.lst

这假设您的排除字符串在 rem.txt 中并且在 files.lst 中有一个文件列表。

留出空间以便于评论:

NR==FNR {               # suck the first file into the indices of an array,
  a[[=11=]]
  next
}

{
  for (i in a) {        # for each file we step through the array,
    n=index([=11=],i)       # search for an occurrence of this string,
    if (n) {            # and if found,
      [=11=]=substr([=11=],0,n-1)substr([=11=],n+length(i))
                        # rewrite the line with the string missing,
    }
  }
}

1                       # and finally, print the line.

如果您将上述脚本存放在一个文件中,例如 foo.awk,您可以 运行 将其作为:

$ awk -f foo.awk rem.txt files.lst

查看生成的文件。

请注意,这只是向您展示了如何构建新文件名。如果您想要对目录中的每个文件 do 执行此操作,最好避免 运行 直接从 awk 重命名,并使用为处理文件而设计的 shell 构造,例如 for 循环:

for f in path/to/*.jpg; do
  mv -v "$f" "$(awk -f foo.awk rem.txt - <<<"$f")"
done

这应该很明显,除了 awk 选项,它们是:

  • -f foo.awk,使用此文件名中的 awk 脚本,
  • rem.txt,您的删除字符串列表,
  • -,表示除 rem.txt
  • 外还应使用标准输入的连字符
  • <<<"$f","here-string" 将输入提供给 awk。

请注意,此 awk 脚本将适用于 gawk 和 macos 中包含的非 GNU awk。