Somente para o caso mostrado, em que há duas colunas em cada arquivo e três arquivos:
$ join -t '|' -o0,1.2,2.2 -a 1 -a 2 test[12].txt | join -t '|' -o0,1.2,1.3,2.2 -a 1 -a 2 - test3.txt
1|1|4|7
2|2|5|8
3||6|9
4|||10
Isto é, execute uma junção externa completa relacional nos dois primeiros arquivos e junte a saída daquele com o terceiro arquivo da mesma maneira. É o -a 1 -a 2
que faz dele uma junção externa completa. Com o GNU join
, você seria capaz de substituir a opção -o
e seu argumento de opção por -o auto
.
Isso pode ser generalizado em um script:
#!/bin/sh
# sanity check
if [ "$#" -lt 2 ]; then
echo 'require at least two files' >&2
exit 1
fi
# temporary files
result=$(mktemp) # the result of a join
tmpfile=$(mktemp) # temporary file holding a previous result
# remove temporary files on exit
trap 'rm -f "$result" "$tmpfile"' EXIT
# join the first two files
join -t '|' -o auto -a 1 -a 2 "$1" "$2" >"$result"
shift 2
# loop over the remaining files, adding to the result with each
for pathname do
mv "$result" "$tmpfile"
join -t '|' -o auto -a 1 -a 2 "$tmpfile" "$pathname" >"$result"
done
# done, output result
cat "$result"
Esse script depende do GNU join
para a opção -o auto
e presume que a união ocorrerá no primeiro campo |
-delimited em cada arquivo e que os arquivos são classificados lexicograficamente neste campo. / p>
Ele une os dois primeiros arquivos e, em seguida, adiciona ao resultado dessa junção, uma vez para cada arquivo restante.
Primeiro exemplo na pergunta:
$ ./script.sh test[123].txt
1|1|4|7
2|2|5|8
3||6|9
4|||10
Segundo exemplo na pergunta (observe que na pergunta, o número errado de campos vazios é mostrado):
$ ./script.sh test[123].txt
1|1|2|4|7
2|3|4|5|8
3|||6|9
4||||10
Se os arquivos não estiverem classificados, você poderá ordená-los em qualquer lugar (note: alternando para bash
aqui para as substituições do processo):
#!/bin/bash
# sanity check
if [ "$#" -lt 2 ]; then
echo 'require at least two files' >&2
exit 1
fi
# temporary files
result=$(mktemp) # the result of a join
tmpfile=$(mktemp) # temporary file holding a previous result
# remove temporary files on exit
trap 'rm -f "$result" "$tmpfile"' EXIT
# join the first two files
join -t '|' -o auto -a 1 -a 2 \
<( sort -t '|' -k1,1 "$1" ) \
<( sort -t '|' -k1,1 "$2" ) >"$result"
shift 2
# loop over the remaining files, adding to the result with each
for pathname do
mv "$result" "$tmpfile"
# note: $tmpfile" would already be sorted
join -t '|' -o auto -a 1 -a 2 \
"$tmpfile" \
<( sort -t '|' -k1,1 "$pathname" ) >"$result"
done
# done, output result
cat "$result"
Para permitir que o usuário participe de outro campo (com -f
), use outro delimitador (com -d
) e use outro tipo de associação (com -j
),
#!/bin/bash
# default values
delim='|'
field='1'
join_type=( -a 1 -a 2 ) # full outer join by default
# override the above defaults with options given to us by the user
# on the command line
while getopts 'd:f:j:' opt; do
case "$opt" in
d) delim="$OPTARG" ;;
f) field="$OPTARG" ;;
j)
case "$OPTARG" in
inner) join_type=( ) ;;
left) join_type=( -a 1 ) ;;
right) join_type=( -a 2 ) ;;
full) join_type=( -a 1 -a 2 ) ;;
*) printf 'unknown join type "%s", expected inner, left, right or full\n' "$OPTARG" >&2
exit 1
esac ;;
*) echo 'error in command line parsing' >&2
exit 1
esac
done
shift "$(( OPTIND - 1 ))"
# sanity check
if [ "$#" -lt 2 ]; then
echo 'require at least two files' >&2
exit 1
fi
# temporary files
result=$(mktemp) # the result of a join
tmpfile=$(mktemp) # temporary file holding a previous result
# remove temporary files on exit
trap 'rm -f "$result" "$tmpfile"' EXIT
# join the first two files
join -t "$delim" -j "$field" -o auto "${join_type[@]}" \
<( sort -t "$delim" -k"$field,$field" "$1" ) \
<( sort -t "$delim" -k"$field,$field" "$2" ) >"$result"
shift 2
# loop over the remaining files, adding to the result with each
for pathname do
mv "$result" "$tmpfile"
# note: $tmpfile would already be sorted and
# the join field is the first field in that file
join -t "$delim" -2 "$field" -o auto "${join_type[@]}" \
"$tmpfile" \
<( sort -t "$delim" -k "$field,$field" "$pathname" ) >"$result"
done
# done, output result
cat "$result"
Teste executando novamente o segundo exemplo:
$ ./script.sh test[123].txt
1|1|2|4|7
2|3|4|5|8
3|||6|9
4||||10
Em execução nos mesmos arquivos, mas ingressando no segundo campo:
$ ./script.sh -f 2 test[123].txt
1|1|2||
10||||4
3|2|4||
4|||1|
5|||2|
6|||3|
7||||1
8||||2
9||||3
Fazendo uma junção interna:
$ ./script.sh -j inner test[123].txt
1|1|2|4|7
2|3|4|5|8