0

我之前问过一个类似的问题

提取文件列表并创建包含此列表的新文件

然而,这一次更具挑战性:

我目前正在处理一个包含大约 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”

4

7 回答 7

1

使用 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) =)

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

希望这可以帮助!

于 2012-10-02T19:04:25.160 回答
1
> 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
于 2012-10-03T12:09:02.303 回答
1

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

sed '$!N;s/^\(\(.*\)_.*_.*\)\n/\2=\1 /' file
于 2012-10-02T22:38:31.343 回答
0
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}'

谢谢

于 2012-10-03T06:25:44.987 回答
0

使用简短的 awk one-liner:

awk -F'_' '{if (NR % 2) {printf("%s_%s=%s", $1, $2, $0)} else {print}}' FILE
于 2012-10-02T19:38:53.987 回答
0

使用 sed:

sed 'N;s/\n/ /;s/\([^_]*_Apple\)/\1=\1/'
于 2012-10-02T19:42:51.807 回答
0

使用 Perl:

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

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

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

于 2012-10-02T18:37:00.903 回答