我想连接来自不同目录的多个文件。
目录 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
只需使用zsh
shell 即可:
cat -- */Chr<1-22>/*.<1-22>(n) > final_file
在 中zsh
,<x-y>
是一个匹配十进制整数范围的 glob 运算符,并且n
glob 限定符会切换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