循环通过文件名列表并通过变量/数组进行迭代,使用bash从文件名中删除所有字符串

时间:2018-02-04 22:58:35

标签: arrays bash macos special-characters nested-loops

我在变量中有一个字符串列表,并希望从文件名列表中删除这些字符串。我从一个文件中提取该字符串,我可以添加并随时修改。变量中的一些字符串可能包括需要删除的项目的一部分,而另一个可能是列表中的另一行。这就是为什么我需要循环通过整个变量列表。

我很熟悉使用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应返回以下行

-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"
每次循环后

返回以下结果

# 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

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

如果有人通过各种方式更好地做到这一点,那就不一定是我上面列出的方式了。

3 个答案:

答案 0 :(得分:2)

您可以使用此awk one-liner获取新文件名:

$ awk 'NR==FNR{a[$0];next} {for(i in a){n=index($0,i);if(n){$0=substr($0,0,n-1)substr($0,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[$0]
  next
}

{
  for (i in a) {        # for each file we step through the array,
    n=index($0,i)       # search for an occurrence of this string,
    if (n) {            # and if found,
      $0=substr($0,0,n-1)substr($0,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

查看生成的文件。

请注意,这只是向您展示如何构建新的文件名。如果你想要的是 这对于目录中的每个文件,最好避免直接从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",&#34; here-string&#34;向awk提供输入。

请注意,这个awk脚本可以同时使用gawk和macos中包含的非GNU awk。

答案 1 :(得分:1)

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

我假设您有一个名为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

与从互联网上下载的所有内容一样,首先进行备份,然后运行文件的小型复制子集,直到您了解其工作原理为止。

答案 2 :(得分:1)

如果您使用自制软件作为软件包管理器,则可以使用以下命令安装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中的所有字符串,并从中删除任何回车符和换行符并引用任何元字符
  • foreach将每个删除应用于rename$_存储的当前文件名

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

这应该是相同的,只是更短:

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