将大量文件通过管道传输到标准输入,提取第一列,然后将它们合并到一个新文件中
Pipe a lot of files to stdin, extract first columns, then combine those in a new file
假设我们有这两个文件:
$ cat ABC.txt
ABC DEF
$ cat PQR.txt
PQR XTZ
并且我们想形成一个 new 文件,每个文件的第一列。这可以通过以下方式实现:
$ paste -d ' ' <(cut -d ' ' -f 1 ABC.txt) <(cut -d ' ' -f 1 PQR.txt )
ABC PQR
但我想将它用于输入中的大量文件,不仅是 ABC.txt 和 PQR.TXT,还有很多。我们如何概括这种情况以将集合中的每个文件传递给 cut,然后将所有输出传递给 paste(我知道这可能是awk 做得更好,但我想知道如何使用这种方法解决这个问题。
编辑 1
我发现了一种肮脏、肮脏的方法:
$ str=''; for i in *.txt; \
do str="${str} <(cut -d ' ' -f 1 ${i})"; \
done ; \
str="paste -d ' ' $str"; \
eval $str
但是,请用一个不涉及进入计算机科学地狱的答案释放我的灵魂。
编辑 2
如果重要的话,每个文件可以有 n 行。
进程替换 <(somecommand)
不会通过管道传输到标准输入,它实际上会在单独的文件描述符上打开一个管道,例如63,并传入 /dev/fd/63
。当这个 "file" 打开时,内核*复制 fd 而不是打开一个真实的文件。
我们可以通过打开一堆文件描述符然后将它们传递给命令来做类似的事情:
# Start subshell so all files are automatically closed
(
fds=()
n=0
# Open a new fd for each process subtitution
for file in ./*.txt
do
exec {fds[n++]}< <(cut -d ' ' -f 1 "$file")
done
# fds now contain a list of fds like 12 14
# prepend "/dev/fd/" to all of them
parameters=( "${fds[@]/#//dev/fd/}" )
paste -d ' ' "${parameters[@]}"
)
{var}< file
是 bash 的动态文件描述符分配语法。像 var=4; exec 4< file;
但不必对 4 进行硬编码,而是让 bash 选择一个免费的文件描述符。 exec
在当前 shell 打开它。
* Linux、FreeBSD、OpenBSD 和 XNU/OSX 无论如何。这不是 POSIX,但也不是 <(..)
仔细观察后,我发现@that-other-guy 的回答很棒,但这里还有另一种肮脏的方式,在幕后大致相同。
eval "paste -d' ' "$(find *.txt -printf " <(cut -d' ' -f1 '%f')")
给定 space 分隔的输入文件,并提供“:”是一个安全的分隔符(即如果输入中没有冒号),此 粘贴 到sed 一行有效:
paste -d':' *.txt | sed 's/ [^:]*$//;s/ [^:]*:*/ /g;s/://g'
(POSIX, 没有 eval, exec, bashisms、子外壳或循环。)
假设我们有这两个文件:
$ cat ABC.txt
ABC DEF
$ cat PQR.txt
PQR XTZ
并且我们想形成一个 new 文件,每个文件的第一列。这可以通过以下方式实现:
$ paste -d ' ' <(cut -d ' ' -f 1 ABC.txt) <(cut -d ' ' -f 1 PQR.txt )
ABC PQR
但我想将它用于输入中的大量文件,不仅是 ABC.txt 和 PQR.TXT,还有很多。我们如何概括这种情况以将集合中的每个文件传递给 cut,然后将所有输出传递给 paste(我知道这可能是awk 做得更好,但我想知道如何使用这种方法解决这个问题。
编辑 1
我发现了一种肮脏、肮脏的方法:
$ str=''; for i in *.txt; \
do str="${str} <(cut -d ' ' -f 1 ${i})"; \
done ; \
str="paste -d ' ' $str"; \
eval $str
但是,请用一个不涉及进入计算机科学地狱的答案释放我的灵魂。
编辑 2
如果重要的话,每个文件可以有 n 行。
进程替换 <(somecommand)
不会通过管道传输到标准输入,它实际上会在单独的文件描述符上打开一个管道,例如63,并传入 /dev/fd/63
。当这个 "file" 打开时,内核*复制 fd 而不是打开一个真实的文件。
我们可以通过打开一堆文件描述符然后将它们传递给命令来做类似的事情:
# Start subshell so all files are automatically closed
(
fds=()
n=0
# Open a new fd for each process subtitution
for file in ./*.txt
do
exec {fds[n++]}< <(cut -d ' ' -f 1 "$file")
done
# fds now contain a list of fds like 12 14
# prepend "/dev/fd/" to all of them
parameters=( "${fds[@]/#//dev/fd/}" )
paste -d ' ' "${parameters[@]}"
)
{var}< file
是 bash 的动态文件描述符分配语法。像 var=4; exec 4< file;
但不必对 4 进行硬编码,而是让 bash 选择一个免费的文件描述符。 exec
在当前 shell 打开它。
* Linux、FreeBSD、OpenBSD 和 XNU/OSX 无论如何。这不是 POSIX,但也不是 <(..)
仔细观察后,我发现@that-other-guy 的回答很棒,但这里还有另一种肮脏的方式,在幕后大致相同。
eval "paste -d' ' "$(find *.txt -printf " <(cut -d' ' -f1 '%f')")
给定 space 分隔的输入文件,并提供“:”是一个安全的分隔符(即如果输入中没有冒号),此 粘贴 到sed 一行有效:
paste -d':' *.txt | sed 's/ [^:]*$//;s/ [^:]*:*/ /g;s/://g'
(POSIX, 没有 eval, exec, bashisms、子外壳或循环。)