【问题标题】:Extracting a list of files and creating a new file containing this list [Part 2]提取文件列表并创建包含此列表的新文件 [第 2 部分]
【发布时间】:2012-10-02 18:15:01
【问题描述】:

我之前在

上问过类似的问题

Extracting a list of files and creating a new file containing this list

不过,这一次更具挑战性:

我目前正在处理一个包含大约 1000 个文件的文件夹,我必须从该文件夹中提取一些文件名并创建另一个包含这些文件名的文件(配置文件)。

基本上,该文件夹的文件名格式如下:

1_Apple_A1_someword.txt 
1_Apple_A2_someword.txt
2_Apple_A1_someword.txt 
2_Apple_A2_someword.txt 
3_Apple_A1_someword.txt 
3_Apple_A2_someword.txt

以此类推,直到

1000_Apple_A1_someword.txt
1000_Apple_A2_someword.txt

我想为这些文件中的每一个创建另一个具有“标签”(Unix 变量)的文件,其值是以下格式中每个“标签”的两个文件的名称。 (每个标签的两个文件由选项卡分隔)。此外,“标签”是文件名的一部分(直到单词“Apple”)例如,

1_Apple=1_Apple_A1_someword.txt 1_Apple_A2_someword.txt
2_Apple=2_Apple_A1_someword.txt 2_Apple_A2_someword.txt
3_Apple=3_Apple_A1_someword.txt 3_Apple_A2_someword.txt

等等……直到

1000_Apple=1000_Apple_A1_someword.txt 1000_Apple_A2_someword.txt

你能告诉我一个执行此操作的单行 Unix 命令吗?也许使用“awk”和“sed”

【问题讨论】:

    标签: sed awk extract filenames


    【解决方案1】:

    这可能对你有用(GNU sed):

    sed '$!N;s/^\(\(.*\)_.*_.*\)\n/\2=\1 /' file
    

    【讨论】:

      【解决方案2】:

      使用 sed 脚本:

      #!/bin/sed -nf
      
      : loop
      H
      s/\([^_]*_[^_]*\)_.*/\1/g
      
      t clear_flag
      : clear_flag
      
      $! {
          N
          s/^\([^_]*_[^\n]*\)\n\(\1[^\n]*\)$/\2/
          t loop
      }
      
      x
      s/^\n//
      s/\([^_]*_[^_]*\)_/\1=\1_/
      s/\n/ /gp
      
      s/.*//
      x
      D
      

      我会尽力解释一切。首先,我们有一个循环将所有以相同前缀开头的文件连接在一起。我根据您的示例定义了一个前缀,它被定义为以第二个下划线结尾的字符串。循环由标签定义,使用“:”命令。在这里,我们将循环标记为“循环”。再往下,必要时,我们使用“t”测试命令“跳”回循环的开始处。

      第一个命令是将行追加到保持空间(辅助缓冲区)中。该行在附加之前由 sed 自动以换行符 ('\n') 为前缀。

      第二个命令提取前缀。我们通过捕获一系列非下划线字符 ([^_]*) 来实现这一点,然后是一个下划线,然后是更多非下划线字符。因为这个模式在反斜线括号之间(\(\)),sed 将捕获匹配这个模式的输入并保存到一个名为\1 的辅助变量中(因为它是该行的第一个捕获)。然后我们跳过一个下划线,后跟一个任意字符序列。替换是我们捕获的内容,因此实际上我们只是删除了包括第二个下划线之后的所有内容。

      我们现在使用一种解决方法来清除 seds 内部标志,该标志指示自上一个“t”命令或脚本启动后是否发生了成功的替换。如果替换命令成功,测试命令(“t”)将分支(跳转)到标签,然后清除内部标志。这对于我们下面的第二个“t”命令是必要的。如果它成功或失败(即它是否分支),它仍然会在“clear_flag”标签之后继续执行。

      现在我们使用“{”命令来启动一组命令。但是,我们在它之前有一个地址前缀,sed 使用它来确定它是否应该运行这些命令。在我们的例子中,只有在读取的最后一个输入行不是最后一行时才执行该组(美元符号“$”代表最后一个输入行,“!”代表否定)。

      组中的第一个命令会将输入中的下一行附加到当前模式空间(即工作缓冲区)中。上一行和新行之间用换行符 (\n) 分隔。

      第三个命令将检查新读取的行是否以我们的前缀开头,并删除孤立的前缀(即前一行)。因为我们从前一行保留的前缀中删除了第二个下划线,并且因为我们添加了一个新行,所以隔离的前缀现在在换行符之前结束。因此,捕获的模式现在读取下划线后不是换行符 ([^\n]*) 的字符。在我们捕获隔离前缀之后,我们跳过分隔前一行和新行的换行符,然后我们开始另一个捕获(将存储在\2,因为它是这一行的第二个捕获)。此捕获将(希望)与第二行匹配。希望因为我们要求匹配完全按照第一次捕获中匹配的内容开始(这就是为什么第二次捕获中的第一件事是对第一个 cature 的反向引用,即\1)。之后,我们匹配一个不是换行符的字符序列,并且在第二次捕获之后,我们期望行尾。

      如果最后一个替换命令成功,我们发现新读取的行也有相同的前缀,所以我们现在必须跳回到循环的开头。这就是“t”命令的功能。它将测试自上一个“t”命令以来是否有任何替换命令成功,如果是,则分支到给定标签。在我们的例子中,我们分支(跳转)回“循环”标签。现在我们可以看到为什么我们需要以前的“t”解决方法。没有它,第一个替换命令可能会成功,而我们真正感兴趣的命令可能会失败,并且“t”仍会分支回“循环”标签。

      如果它离开循环,则意味着新读取的行没有相同的前缀。因此我们现在可以打印之前匹配的内容。

      我们首先使用交换(“x”)命令将模式空间的内容与保持空间的内容交换。现在我们的模式空间包含所有具有相同前缀的文件,我们的保持空间包含当前前缀在一个孤立的行中,然后是第一个文件不共享相同前缀的行。

      由于之前我们将所有文件名附加到保持空间,所有文件名都由换行符分隔,并且由于第一个文件名也被附加,因此当前模式空间中的第一个字节是换行符。要删除它,我们只需将其替换为空即可。

      现在我们必须生成作业的格式。这就是为什么我们有一个熟悉的替换命令,我们再次提取前缀,除了现在我们删除了.* 以保持该行的其余部分完整。替换包括前缀(捕获的)、一个等号,我们还恢复了我们从模式空间中的第一个文件中删除的内容:它的前缀和下划线。

      我们几乎准备好打印出该行,但文件名仍由换行符分隔。因此,我们用空格替换所有换行符(g 标志告诉 sed 在输入行上尽可能多地重复替换命令)。由于现在该行已准备就绪,我们可以添加 p 前缀来告诉 sed 打印它。

      最后一步是为下一个前缀准备再次启动脚本。保留空间必须为空,以便用于存储具有新前缀的文件名。那就是我们有一个命令将模式空间中的每个字符都替换为空,然后是一个交换命令。

      存放空间已准备就绪。现在我们必须准备模式空间。它必须仅包含带有新前缀的文件名的第一行。要处于这种状态,我们所要做的就是删除存储在第一行中的旧前缀。我们可以执行类似s/.*\n// 的操作来替换除最后一行的字符(包含带有新前缀的文件名)之外的所有字符,但D 命令会执行此操作并强制脚本重新开始执行读另一行,这样我们就省了一些打字。

      虽然脚本可能有点晦涩难懂,而且描述过于冗长,但一旦你明白发生了什么,它就会开始变得简单(r) =)

      必须提到的一点:输入必须排序(或至少具有相同前缀的文件必须组合在一起)。

      希望这会有所帮助!

      【讨论】:

      • 看起来很有趣(我会研究你的脚本),但对于一个简单的任务来说真的很复杂。为什么要使用这种酷刑装置(sed)来做到这一点?它是一种艺术,就像 sed 中已知的 pacman 或俄罗斯方块?只是想知道。
      • 我真的不知道,但我想我正在测试 sed =) 的极限
      【解决方案3】:
      > ls -1 | perl -F_ -ane 'chomp;if($_=~m/Apple_A/){$X{$F[0]."_".$F[1]}=$X{$F[0]."_".$F[1]}." ".$_;}END{foreach (keys %X){print $_."=".$X{$_}."\n"}}'
      3_Apple= 3_Apple_A1_someword.txt 3_Apple_A2_someword.txt
      2_Apple= 2_Apple_A1_someword.txt 2_Apple_A2_someword.txt
      1_Apple= 1_Apple_A1_someword.txt 1_Apple_A2_someword.txt
      

      【讨论】:

        【解决方案4】:

        使用 Perl:

        perl -pe 'if ($. % 2) { /([0-9]+_Apple)/ and print "$1="; s/\s+$/ /; }'
        

        在奇数行上,匹配 ...Apple,用 = 输出,并将行尾的空格替换为单个空格。

        注意: Unix 变量的名称不能以数字开头。

        【讨论】:

          【解决方案5】:

          使用简短的 awk 单行:

          awk -F'_' '{if (NR % 2) {printf("%s_%s=%s", $1, $2, $0)} else {print}}' FILE
          

          【讨论】:

            【解决方案6】:

            使用 sed:

            sed 'N;s/\n/ /;s/\([^_]*_Apple\)/\1=\1/'
            

            【讨论】:

              【解决方案7】:
              num=1
              while [ $num -le 1000 ]
              do
              echo "${num}_Apple=${num}_Apple_A1_someword.txt ${num}_Apple_A2_somword.txt"
              num=`expr $num + 1`
              done
              

              输出:

              1_Apple=1_Apple_A1_someword.txt 1_Apple_A2_somword.txt
              2_Apple=2_Apple_A1_someword.txt 2_Apple_A2_somword.txt
              3_Apple=3_Apple_A1_someword.txt 3_Apple_A2_somword.txt
              4_Apple=4_Apple_A1_someword.txt 4_Apple_A2_somword.txt
              5_Apple=5_Apple_A1_someword.txt 5_Apple_A2_somword.txt
              ...........
              

              如果数字 1000 不是静态的,那么您可以从文件本身获取值:

              num=`cat file|sort|tail -1|awk -F"_" '{print $1}'
              

              谢谢

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2020-03-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-02-09
                • 1970-01-01
                • 2015-08-23
                相关资源
                最近更新 更多