如果使用5个输入文件,bash xargs和bash粘贴是否可以正确输出3个文件?

时间:2018-11-19 14:57:04

标签: linux bash awk xargs

也许xargs可以做到,也许不能,但是似乎可行。该解决方案根本不需要使用xargs。希望使用所有bash命令,但不建议使用python。尽管它必须处理大量的输入文件(此处仅显示玩具大小的示例),因此不要尝试将所有文​​件的内容预先加载到内存中。

起始输入是一个文本文件'docs.txt'中的5个文件名,全部位于一列中:

[ga@sam ~]$ cat docs.txt
a.1.txt
a.2.txt
b.1.txt
c.1.txt
c.2.txt

所需的输出正好是3个文件:输出文件a.doc将按此顺序包含a.1.txt和a.2.txt的内容。输出文件b.doc:b.1.txt的内容。输出文件c.doc:文件c.1.txt和c.2.txt的内容按此顺序。

我当前正在做的是xargs正在接收3行输入,并且gnu paste连接了每行列出的文件内容。我希望xargs能够准确输出3个文本文件,每xargs输入行一个,如上所述,根据所解释的每个group-by值命名,但是我没有找到窍门。

这是到目前为止的代码:

[ga@sam ~]$ cat docs.txt | awk -F. '{ORS=" "}NR==1 {prev=$1; print; next} prev!=$1{print "\n";}{prev=$1}1' | xargs -L 1 paste -s
my cat
has fleas
my dog is clean
the bat
ate a rat
[ga@sam ~]$ cat docs.txt | awk -F. '{ORS=" "}NR==1 {prev=$1; print; next} prev!=$1{print "\n";}{prev=$1}1' # | xargs -L 1 paste -s
a.1.txt a.2.txt
 b.1.txt
 c.1.txt c.2.txt [ga@sam ~]$
[ga@sam ~]$ cat docs.txt | awk -F. '{ORS=" "}NR==1 {prev=$1; print; next} prev!=$1{print "\n";}{prev=$1}1' | xargs -L 1 -P 0 --process-slot-var=f paste -s > "$f".doc
xargs: unrecognized option '--process-slot-var=f'

这里awk的目的仅仅是对文件名的第一个字段进行分组(例如SQL分组)。通过这种方式,希望每个组都只创建一个输出文件。

粘贴在这里的目的就像猫。我将所有文件依次连接在一起。如果我们要使用cat而不是paste,那么它的工作可能也同样好,如果它比paste慢一点,并且在3次调用中cat命令看起来像这样:

cat a.1.txt a.2.txt > a.doc
cat b.1.txt > b.doc
cat c.1.txt c.2.txt > c.doc

但是,正如我试图解释的那样,我不想预先明确地编写3行代码,因为它将完全根据输入文件中的组来动态确定输出文件的数量。

即使我将xargs升级到最新版本,我仍然希望严重无法使用上面所示的代码编写3个输出文件。 xargs -process-slot-var似乎根据系统特征生成了多个文件,而不是此应用程序中的3个文件,更重要的是,输出文件的数量直接根据实际应用程序中找到的组数而变化。

如果单行代码行不通,我可能会退后一步,使用某种循环结构(在awk中?)进行一些变量替换,最终每个输出文件发出一行bash命令。我不太了解awk以发出命令。如果采用这种方式,我宁愿使用bash parallel来并行运行这些行,因为如本应用程序中所述,将有数百万个输出文件。

感谢创意。

4 个答案:

答案 0 :(得分:5)

您可以使用剪切和排序来提取组,然后使用一会儿读取循环将组文件整理在一起:

cut -d. -f1 docs.txt |
  sort -u |
  while read -r group; do cat "$group".*.txt > "$group".doc; done

另外,普通打击

while IFS=. read -r group rest; do
    cat "$group.$rest" >> "$group.doc"
done < docs.txt

或普通awk

awk -F. '{
    f = $1 ".doc"
    while (( getline line < $0 ) > 0)
        print line > f
    close($0)
}' docs.txt

答案 1 :(得分:1)

能否请您尝试一次以下解决方案。

sort -t'.' -k1 docs.txt | awk -F'.' 'prev!=$1{close(file);file=$1".doc"} {print > file;prev=$1}'

现在也添加一种非衬套形式的解决方案。

sort -t'.' -k1 docs.txt |
awk -F'.' '
  prev!=$1{
    close(file) 
    file=$1".doc"
  }
{
  print > file
  prev=$1
}'

答案 2 :(得分:1)

未经测试,但应该接近:

awk '
    NR==FNR { ARGV[ARGC++]=$0; next }
    FNR==1 { close(out); out=FILENAME; sub(/\..*/,".doc",out) }
    { print >> out }
' docs.txt

答案 3 :(得分:-2)

以下代码是我使用的解决方案。我开发了它,但是没有使用任何其他人的代码提交,部分原因是在我开发它时还没有其他人的提交。在任何情况下,感谢您的所有答复以及答案和评论。下面的代码运行速度很快,并且可以做所有需要的事情。它还没有显式循环,这很有趣。您可能会喜欢看我的最终代码,因为许多人对此感兴趣。最好的祝福。作为政策,只要没有引起我注意的问题,只要没有人为我做出支持(仍然为零),我就会拒绝支持。但我很乐意回馈。

#!/bin/bash
# Inputs from tmp subdir
# Outputs to consolidated subdir
# Please run in dir above tmp
# No pipes allowed in an array element apparently? But PASTING worked OK, maybe since a string contains the pipe.
# The head (below) after INFILESSORT is only for dev speed.
# For dev and debugging only please remove --max-procs=0 which is for parallelism.

INFILESFIND=(find tmp -name "*.doc" -type f)
INFILESSORT=(sort -k1 -k2 -t'.')
GROUPING=(awk -F. '{ORS=" "}NR==1 {prev=$1; print; next} prev!=$1{print "\n";}{prev=$1}1')
PASTING=(xargs --max-procs=0 -L 1 -I filenames sh -c 'echo "filenames" | xargs -L 1 paste -s > consolidated/$(echo $(basename "filenames") | cut -f1 -d.).doc')
# The following line executes the script's arrays that were defined above.
"${INFILESFIND[@]}" | "${INFILESSORT[@]}" | "${GROUPING[@]}" | "${PASTING[@]}"