【问题标题】:Append wc lines to filename将 wc 行附加到文件名
【发布时间】:2017-01-25 04:00:49
【问题描述】:

标题说明了一切。我已经设法得到了这个:

lines=$(wc file.txt | awk {'print $1'});

但我可以使用辅助将其附加到文件名。向我展示了如何在当前目录中的所有 .txt 文件中循环的加分。

【问题讨论】:

  • 您是说将 wc 的输出添加到文件名的末尾还是文件内容的末尾? edit 您的问题包括简洁、可测试的样本输入和预期输出。我很惊讶您在没有示例的情况下获得了 4 个赞成票!我见过几个问题,人们将脚本分隔符 's 放在脚本中 - (awk {'foo'} 而不是 awk '{foo}') - 这个想法到底是从哪里来的???

标签: bash shell awk file-rename wc


【解决方案1】:
find -name '*.txt' -execdir bash -c \
  'mv -v "$0" "${0%.txt}_$(wc -l < "$0").txt"' {} \;

在哪里

  • 为每个 (\;) 匹配的文件执行 bash 命令;
  • {} 被当前处理的文件名替换并作为第一个参数 ($0) 传递给脚本;
  • ${0%.txt} 从字符串后面删除.txt 的最短匹配项(参见official Bash-scripting guide);
  • wc -l &lt; "$0" 仅打印文件中的行数(例如,请参阅this question 的答案)

样本输出:

'./file-a.txt' -> 'file-a_5.txt'
'./file with spaces.txt' -> 'file with spaces_8.txt'

【讨论】:

  • 专业的外观和感觉。此外,只有一个使用wc -l。 ++
  • 你应该用${0%.txt}替换$(basename "$0" .txt)basename在这种情况下绝对没用而且效率低下!也可以在find 谓词中添加-type f
  • @gniourf_gniourf,确实如此。谢谢。
  • 很好的解决方案@RuslanOsmanov。是什么从上面的wc -l 输出中去除了前导空格?
  • @codeforester,据我所知,现代的 wc 实现不会为第一个计数器(尤其是唯一的计数器)打印前导空格:git.savannah.gnu.org/cgit/coreutils.git/tree/src/…
【解决方案2】:

您可以使用rename 命令,它实际上是一个Perl 脚本,如下:

rename --dry-run 'my $fn=$_; open my $fh,"<$_"; while(<$fh>){}; $_=$fn; s/.txt$/-$..txt/' *txt

样本输出

'tight_layout1.txt' would be renamed to 'tight_layout1-519.txt'
'tight_layout2.txt' would be renamed to 'tight_layout2-1122.txt'
'tight_layout3.txt' would be renamed to 'tight_layout3-921.txt'
'tight_layout4.txt' would be renamed to 'tight_layout4-1122.txt'

如果您喜欢它所说的内容,请删除 --dry-run 并再次运行。

脚本在不使用任何外部进程的情况下计算文件中的行数,然后根据您的要求重命名它们,也不使用任何外部进程,因此非常有效。

或者,如果您愿意调用外部进程来计算行数,并避免使用上面的 Perl 方法:

rename --dry-run 's/\.txt$/-`grep -ch "^" "$_"` . ".txt"/e' *txt

【讨论】:

  • rename 的问题在于它以多种形式出现。 Perl 自带的版本很方便,其他的就没有那么强大了。
  • @hek2mgl 是对的。例如,在我的 Gentoo 设置中,rename 来自 sys-apps/util-linux 包 (kernel.org/pub/linux/utils/util-linux),很遗憾,它不支持 Perl。并且 Perl 版本由dev-perl/rename 提供为perl-rename 可执行文件。
【解决方案3】:

使用重命名命令

for file in *.txt; do 
 lines=$(wc ${file} | awk {'print $1'});
 rename s/$/${lines}/ ${file}
done

【讨论】:

    【解决方案4】:
    #/bin/bash
    
    files=$(find . -maxdepth 1 -type f -name '*.txt' -printf '%f\n')
    for file in $files; do
        lines=$(wc $file | awk {'print $1'});
        extension="${file##*.}"
        filename="${file%.*}"
        mv "$file" "${filename}${lines}.${extension}"
    done
    

    您可以相应地调整最大深度。

    【讨论】:

    • 如果任何文件中包含空格或通配符,这将中断 - 分词问题。
    【解决方案5】:

    你也可以这样做:

    for file in "path_to_file"/'your_filename_pattern'
        do
          lines=$(wc $file | awk {'print $1'})
          mv $file $file'_'$lines
        done
    

    示例:

        for file in /oradata/SCRIPTS_EL/text*
        do
            lines=$(wc $file | awk {'print $1'})
            mv $file $file'_'$lines
        done
    

    【讨论】:

      【解决方案6】:

      这可行,但肯定有更优雅的方法。

      for i in *.txt; do
        mv "$i" ${i/.txt/}_$(wc $i | awk {'print $1'})_.txt; 
      done
      

      结果会将行号很好地放在.txt 之前。 喜欢:

      file1_1_.txt 
      file2_25_.txt
      

      【讨论】:

      • 您应该使用 glob 执行 for 循环。不要管ls
      • 已编辑。谢谢。
      • 你能解释一下这背后的原因吗?谢谢
      • 还可能想要双引号目标文件名(或至少是其中的"${i/...." 部分)
      • 这里不需要 bashism ${i/.txt}${i%.txt} 也可以完成这项工作(并且不会因为 file.txt.lalala.txt 形式的文件名而中断);如上所述,您应该引用 all 变量扩展;如果您使用wc -l &lt; "$i"awk 将变得无用(并且您的脚本将更加高效,并且不会因包含换行符的文件名而中断)。
      【解决方案7】:

      您可以使用grep -c '^' 来获取行数,而不是wcawk

      for file in *.txt; do
        [[ ! -f $file ]] && continue # skip over entries that are not regular files
        #
        # move file.txt to file.txt.N where N is the number of lines in file
        #
        # this naming convention has the advantage that if we run the loop again,
        # we will not reprocess the files which were processed earlier
        mv "$file" "$file".$(grep -c '^' "$file")
      done
      

      【讨论】:

        【解决方案8】:
        { linecount[FILENAME] = FNR }
        END {
            linecount[FILENAME] = FNR
            for (file in linecount) {
                newname = gensub(/\.[^\.]*$/, "-"linecount[file]"&", 1, file)
                q = "'"; qq = "'\"'\"'"; gsub(q, qq, newname)
                print "mv -i -v '" gensub(q, qq, "g", file) "' '" newname "'"
            }
            close(c)
        }
        

        将上述awk 脚本保存在一个文件中,比如wcmv.awk,运行如下:

        awk -f wcmv.awk *.txt
        

        它将列出需要运行的命令以以所需的方式重命名文件(除了它将忽略空文件)。要实际执行它们,您可以将输出通过管道传输到 shell 以执行如下。

        awk -f wcmv.awk *.txt | sh
        

        就像所有不可逆的批处理操作一样,请小心并仅在看起来不错的情况下执行命令。

        【讨论】:

        • 从一开始就是糟糕的设计:你在混合代码和数据!如果文件名包含引号怎么办?
        • @gniourf_gniourf 我认为文件名中的单引号现在不会造成任何问题。好吧,这段代码现在看起来确实值得。 :P
        【解决方案9】:
         awk '
          BEGIN{ for ( i=1;i<ARGC;i++ ) Files[ARGV[i]]=0 }
        
          {Files[FILENAME]++}
        
          END{for (file in Files) {
                # if( file !~ "_" Files[file] ".txt$") {
        
                   fileF=file;gsub( /\047/, "\047\"\047\"\047", fileF)
                   fileT=fileF;sub( /.txt$/, "_" Files[file] ".txt", fileT)
        
                   system( sprintf( "mv \047%s\047 \047%s\047", fileF, fileT))
        
                #   }
                }
             }' *.txt
        

        另一种使用 awk 的方法是通过允许对名称进行更多控制来更轻松地管理第二个循环(例如避免一个已经从上一个循环中获得计数的人)

        由于@gniourf_gniourf 的好评:

        • 文件名可以带空格
        • 小代码现在对于这么小的任务来说很繁重

        【讨论】:

        • 从一开始就是糟糕的设计:你正在_混合代码和数据!如果文件名包含引号怎么办?
        • 你是对的,我忘了假设这一点。还有第二个问题,永远无法访问的空文件(使用 ARGV 解决了这个问题,但会产生一个名字空洞的问题,......)尝试了几个技巧,但它创建了一个 gaz 工厂(模块化但不足以满足如此小的请求)。我把最后一个版本假设为更好
        • 文件名内空格和单引号的改编代码
        猜你喜欢
        • 2016-07-20
        • 2011-12-06
        • 1970-01-01
        • 1970-01-01
        • 2017-09-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-25
        相关资源
        最近更新 更多