连接不同目录中的多个文件

连接不同目录中的多个文件

我想连接来自不同目录的多个文件。

目录 1:Chr1包含(在本示例中)四个文件:

ABC.1 
DEF.1
GHI.1 
JKL.1 

目录2:Chr2

ABC.2  
DEF.2 
GHI.2
JKL.2 

有22个目录。每个文件有 20 列和标题。所有文件的标头都相同。

我想将所有内容连接到一个文件中(一个全局输出文件,用于连接所有目录中的所有文件)。

我尝试过这个,但这不起作用。

cat */Chr{1..22}/*.{1..22} > */final_file

说“没有这样的文件或目录”,因为没有文件,例如*.1~21为chr22目录下的文件。

你有什么想法?先感谢您。

答案1

只需使用zshshell 即可:

cat -- */Chr<1-22>/*.<1-22>(n) > final_file

在 中zsh<x-y>是一个匹配十进制整数范围的 glob 运算符,并且nglob 限定符会切换numericglobsort导致 glob 扩展按数字排序的选项。

在另一个 shell 中,您可以执行以下操作:

zsh -c 'cat -- */Chr<1-22>/*.<1-22>(n) > final_file'

要跳过除第一个文件之外的所有文件的标头,并假设 GNU 或 busybox 实现tail(在使用 Linux 作为内核的系统上最常见),您可以执行以下操作:

(){
  cat < $1; shift; (($#)) && tail -qn +2 -- "$@"
} */Chr<1-22>/*.<1-22>(n) > final_file 

答案2

您的方法的问题在于,重复的通配符不会以“同步”方式解释(=“展开”),而是对于命令行上的每次出现都进行重新解释和独立。因此,您将需要使用嵌套的 shell 循环进行操作。

您可以尝试以下 shell 脚本。请注意,它使用bash功能(您的问题不包括您正在使用的 shell)

#!/bin/bash

hdr=0   # initialize variable to keep track of whether the header is already printed

# loop over directories
for d in Chr*
do
    # extract trailing number from dir name by removing 'Chr' part (bash feature!)
    n="${d#Chr}"

    # loop over all files
    for f in "$d/"*".$n"
    do
       if (( hdr == 0 )) # if header wasn't printed yet, output entire file
       then
           cat "$f" > final_file
           hdr=1
       else              # otherwise, output file content starting with line 2
           tail -n +2 "$f" >> final_file
       fi
    done
done

您可以将该脚本命名concatenate.sh为可执行文件,然后从所有子目录所在的目录运行它Chr{1..22}。也将final_file在该目录中创建。

请注意,我无法对其进行太远的测试,但它不应该破坏任何东西......

答案3

如果你想捕获所有子目录中的所有文件,Chr.*你可以使用这个

cat Chr*/* >final_file

如果您需要限制每个子目录中的文件集以匹配该目录名称的后缀(因此Chr1我们只考虑文件匹配*.1),您将需要一个循环

shopt nullglob    # This is bash-specific
for i in {1..22}
do
    cat Chr$i/*.$i
done >final_file

该选项shopt nullglob告诉 shell,如果通配符无法匹配,则将其删除,而不是保留为字面星号。

作为替代方案,由于似乎感觉您希望从连接的文件中省略除第一个标题行之外的所有内容,因此此扩展循环可以处理它

first=yes
for i in {1..22}
do
    for f in Chr$i/*.$i
    do
        [[ -n "$first" ]] && head -n1 "$f" && first=
        cat "$f"
    done
done >final_file

或者,如果您的标题行作为第一个文件的第一行存在,并且此后可以在遇到它的任何地方将其删除,则可以使用如下结构将其删除

for i in {1..22}
do
    cat Chr$i/*.$i
done |
    awk '$0 != header { print } header == "" { header = $0 }' >final_file

相关内容