【问题标题】:Merge multiple files preserving the original sequence in unix合并多个文件,保留unix中的原始序列
【发布时间】:2015-03-30 07:43:20
【问题描述】:

我的目录中有多个(超过100个)文本文件,例如

files_1_100.txt
files_101_200.txt

文件的内容是一些变量的名字,比如files_1_100.txt包含一些1到100之间的变量名

"var.2"
"var.5"
"var.15"

同样files_201_300.txt 包含一些介于 101 到 200 之间的变量

"var.203"
"var.227"
"var.285"

files_1001_1100.txt 一样

"var.1010"
"var.1006"
"var.1025"

我可以使用命令合并它们

cat files_*00.txt > ../all_files.txt

但是,文件的内容并不遵循父文件中的内容。例如all_files.txt 显示

"var.1010"
"var.1006"
"var.1025"
"var.1"
"var.5"
"var.15"
"var.203"
"var.227"
"var.285"

那么,我怎样才能确保files_1_100.txt 的内容首先出现,然后是files_201_300.txt,然后是files_1001_1100.txt,这样all_files.txt 的内容就是

"var.1"
"var.5"
"var.15"
"var.203"
"var.227"
"var.285"
"var.1010"
"var.1006"
"var.1025"

【问题讨论】:

  • 我建议以某种已知格式重命名它们——比如files_001_100.txt,然后在它们上面运行 cat..
  • 为什么不在之后对文件/输出进行排序?

标签: linux bash shell unix command-line


【解决方案1】:

让我试一试,但我认为这会奏效:

ls file*.txt | sort -n -t _ -k2 -k3 | xargs cat

我们的想法是获取文件列表并对其进行排序,然后将它们传递给 cat 命令。

排序使用几个选项:

  • -n - 使用数字排序而不是字母排序
  • -t _ - 使用下划线字符将输入(文件名)划分为字段
  • -k2 -k3 - 首先按第二个字段排序,然后按第三个字段(2 个数字)

您说过您的文件名为 file_1_100.txt、file_101_201.txt 等。如果这意味着(似乎表明)第一个数字“块”始终是唯一的,那么您可以省略 -k3旗帜。仅当您最终使用 file_100_2.txt 和 file_100_10.txt 时才需要该标志,您必须查看第二个数字“块”以确定首选顺序。

根据您正在使用的文件数量,您可能会发现指定 glob (file*.txt) 可能会使 shell 不堪重负,并导致有关行太长的错误。如果是这种情况,您可以这样做:

ls | grep '^file.*\.txt$' | sort -n -t _ -k2 -k3 | xargs cat

【讨论】:

  • 这个命令似乎也使用了备份文件,即那些以波浪号(~)结尾的文件。有时,即使我关闭它们,编辑器也会将它们留在那里。有没有办法在这里忽略它。
  • 添加了一个 glob 以将文件限制为仅以“file”开头且仅以“.txt”结尾的文件 - 如果您需要或多或少具体,可以轻松更改该 glob.. .
  • ls 解析输出很容易出错,因为如果file*.txt 包含空格或换行符,这将中断。
  • 没错,但“规范”的一部分确实为我们提供了预期的文件名形式。如果 OP 没有说任何相关的文件名,那么我们将不得不允许它。但是由于 q 非常具体地说明了文件名的形成方式,所以我认为我处于相当安全的基础上......
  • 请注意每个(工作)答案如何假定块之间的下划线。我们基于问题中的第一句话和示例...
【解决方案2】:

您可以使用printf sort 并将其传递给xargs cat

printf "%s\0" f*txt | sort -z -t_ -nk2 | xargs -0 cat > ../all_files.txt

请注意,整个管道都在处理以 NULL 结尾的文件名,因此确保此命令甚至可以处理带有空格/换行符等的文件名。

【讨论】:

  • 我发现我们也需要使用第三个参数 -k3 进行排序才能使命令正常工作。
  • 当然可以:printf "%s\0" f*txt | sort -z -t_ -nk2,3 | xargs -0 cat 也可以。
【解决方案3】:

如果您的文件名没有任何特殊字符或空格,那么其他答案应该是简单的解决方案。 否则,试试这个基于rename 的方法:

$ ls files_*.txt
files_101_200.txt  files_1_100.txt

$ rename  's/files_([0-9]*)_([0-9]*)/files_000$1_000$2/;s/files_0*([0-9]{3})_0*([0-9]{3})/files_$1_$2/' files_*.txt

$ ls files_*.txt
files_100_100.txt  files_101_200.txt

$ cat files_*.txt > outputfile.txt

$ rename 's/files_0*([0-9]*)_0*([0-9]*)/files_$1_$2/' files_*.txt

【讨论】:

  • 我通常更喜欢这样的文件名应该用零填充。使排序更容易。
【解决方案4】:

cat file_* 的默认排序行为是按字母顺序排列,而不是按数字排列。

按数字顺序列出它们,然后对每一个进行分类,将输出附加到某个文件中。

ls -1| sort -n |xargs -i cat {} >> file.out

【讨论】:

  • 我认为由于所有文件的文件名都以字母开头,因此该解决方案似乎不起作用。
  • 它没有用。我得到的输出文件仍然是“var.1010”“var.1006”“var.1025”“var.1”“var.5”“var.15”“var.203”“var.227”“var. 285"
【解决方案5】:

您可以尝试使用 for 循环并一一添加文件(当数字未填充零时,-v 对文件进行正确排序)

for i in $(ls -v files_*.txt)
do
    cat $i >> ../all_files.txt
done

或者更方便的一行:

for i in $(ls -v files_*.txt) ; do cat $i >> ../all_files.txt ; done

【讨论】:

    【解决方案6】:

    您也可以使用 Awk 通过拆分和排序 ARGV 来做到这一点:

    awk 'BEGIN {
        for(i=1; i<=ARGC-1; i++) {
            if(i > 1) {
                j=i-1
                split(ARGV[i], curr, "_")
                split(ARGV[j], last, "_")
                if (curr[2] < last[2]) {
                    tmp=ARGV[i]
                    ARGV[i]=ARGV[j]
                    ARGV[j]=tmp
                }
            }
        }
    }1' files_*00.txt
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-21
      • 1970-01-01
      • 1970-01-01
      • 2021-09-26
      • 2014-10-05
      • 1970-01-01
      相关资源
      最近更新 更多