如何在列表中包含空格时分隔单个文件名

时间:2015-03-10 15:57:07

标签: regex bash shell unix awk

我正在编写一个bash脚本,用于根据SHA-1哈希值生成重复文件列表。然后,我希望从包含目录中所有文件名的数组中删除这些重复项,因此我只能将非重复文件复制到新目录。

我面临的问题是,许多文件的名称中都包含空格,因此我不知道如何将它们与列表分开。

# Fill array with all file names in directory
files=(*)

#find all repeating sha-1 values
repeats=$(echo $(find -type f -exec sha1sum '{}' ';' | sort | uniq --all-repeated=separate -w 40 ))

由于名称中的空格,输出采用以下格式:

1386d44b318730ffa98a34176d4e8b7eab8e02a4 ./Forensic Scripting 01 - Introduction to Developing Software and Shell Scripting (1).ppt 1386d44b318730ffa98a34176d4e8b7eab8e02a4 ./Forensic Scripting 01 - Introduction to Developing Software and Shell Scripting.ppt 2f4fc07ee944d666c34b0dfeeda90ad1c5cb9e71 ./kravica_waterfall_bosnia (copy).jpg 2f4fc07ee944d666c34b0dfeeda90ad1c5cb9e71 ./kravica_waterfall_bosnia.jpg 45f478cedd980ff2313f05fd0997a08492b9b21b ./canada-niagarafalls_-5 (another copy).jpg 45f478cedd980ff2313f05fd0997a08492b9b21b ./canada-niagarafalls_-5 (copy).jpg 45f478cedd980ff2313f05fd0997a08492b9b21b ./canada-niagarafalls_-5.jpg a3c6c5b749ce43cc3dade17230580b5ecf4d1557 ./frink (copy).png a3c6c5b749ce43cc3dade17230580b5ecf4d1557 ./frink.png d6039f1932dc2bb6fecfa41c02a7e9bc6656c621 ./UK - Associate Guide (Final Version -Effective Date 13 Feb 2012) (copy).pdf d6039f1932dc2bb6fecfa41c02a7e9bc6656c621 ./UK - Associate Guide (Final Version -Effective Date 13 Feb 2012).pdf

如何将文件名包装在引号中,以便将它们清楚地标识为一个块,以便我能够根据这些列表执行操作?

2 个答案:

答案 0 :(得分:1)

如果你有bash 4,你可以使用关联数组,这会使这个问题变得更简单。 (下面的bash 3解决方案)。

例如:

# The output array
declare -a nondups=()
# An associative array which maps checksum to filename
declare -A checksum

for file in *; do
  # Make sure it's not a directory
  if [[ -f "$file" ]]; then
    chk=$(sha1sum "$file")
    # We just want the checksum
    chk=${chk%% *}
    if ! [[ -v checksum[$chk] ]]; then
    # -v doesn't work on subscripts before v4.3. Alternative:
    # if [[ -z ${checksum[$chk]} ]]; then
      # We've never hit this checksum before
      nondups+=($file)  # Add it to the list
      checksum[$chk]=1  # Mark checksum as seen
    fi
  fi
done

如果您想要执行递归文件列表,就像find那样,您可以使用globstar shell选项来执行递归列表。确保您已完成shopt -s globstar,然后将for循环更改为for file in **; do

如果您没有bash 4,可以将checksum设为一个简单的字符串变量,并使用子字符串匹配进行检查:

if [[ $checksum == *${chk}* ]]; then
  # We've never hit this checksum before
  nondups+=($file)  # Add it to the list
  checksum+=" $chk" # Mark checksum as seen
fi

答案 1 :(得分:0)

我知道它并不是你提出的要求,但我个人建议 - 使用perl。

#!/usr/bin/perl
use strict;
use warnings;
use Digest::SHA1 qw ( sha1_hex );

my $search = "*"; 

my %digests; 

while ( my $filename = glob ( $search ) ) {

    next if -d $filename;
    open ( my $input, "<", $filename ) or warn $!;
    my $sha_sum = sha1_hex ( <$input> ); 
    close ( $input );
    if ( $digests{$sha_sum} ) { print "$filename matches $digests{$sha_sum}\n"; }
    $digests{$sha_sum} = $filename;

    print "$filename $sha_sum\n";
 }

这样 - 您可以根据需要操纵$filename,而不必担心空格等。(这不会打印所有重复项,因为它只跟踪最后一个重复项) 。

完成后,您可以:

foreach my $filename ( values %digests ) { 
   print "$filename is unique\n";
}

(因为%digests中的任何非唯一条目已被删除)