서로 다른 행/행이 있는 여러 개의 파이프로 구분된 파일을 첫 번째 열을 기준으로 하나의 파일로 연결합니다.
전임자:
테스트1.txt
1|1
2|2
test2.txt
1|4
2|5
3|6
테스트 3.txt
1|7
2|8
3|9
4|10
산출:
1|1|4|7
2|2|5|8
3||6|9
4|||10
예 2: test1.txt
1|1|2
2|3|4
test2.txt
1|4
2|5
3|6
테스트 3.txt
1|7
2|8
3|9
4|10
산출:
1|1|2|4|7
2|3|4|5|8
3||||6|9
4|||||10
답변1
각 파일에 2개의 열과 3개의 파일이 있는 표시된 사례에만 적용됩니다.
$ 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
즉, 처음 두 파일에 대해 관계형 완전 외부 조인을 수행하고 동일한 방식으로 해당 출력을 세 번째 파일과 조인합니다. 이것이 -a 1 -a 2
완전한 외부 연결이 되는 이유입니다. GNU를 사용하면 join
옵션 -o
과 해당 옵션 인수를 -o auto
.
이는 스크립트로 요약될 수 있습니다.
#!/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"
스크립트는 GNU의 join
옵션 에 의존하며 각 파일의 첫 번째 구분 필드에서 -o auto
연결이 발생하고 |
파일이 이 필드에서 사전순으로 정렬된다고 가정합니다.
처음 두 파일을 연결한 다음 남은 각 파일에 대해 한 번씩 해당 연결 결과를 추가합니다.
질문의 첫 번째 예:
$ ./script.sh test[123].txt
1|1|4|7
2|2|5|8
3||6|9
4|||10
질문의 두 번째 예(질문에는 잘못된 수의 빈 필드가 표시됩니다):
$ ./script.sh test[123].txt
1|1|2|4|7
2|3|4|5|8
3|||6|9
4||||10
파일이 정렬되지 않은 경우 언제든지 정렬할 수 있습니다(참고: bash
프로세스 대체를 위해 여기로 전환).
#!/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"
사용자가 다른 필드에 참가할 수 있도록 허용하려면( 사용 -f
) 다른 구분 기호를 사용 -d
하고( 사용) 다른 결합 유형을 사용합니다( 사용 -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"
두 번째 예제를 다시 실행하여 테스트합니다.
$ ./script.sh test[123].txt
1|1|2|4|7
2|3|4|5|8
3|||6|9
4||||10
동일한 파일에서 실행하지만 두 번째 필드에서 조인합니다.
$ ./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
내부 조인을 만듭니다.
$ ./script.sh -j inner test[123].txt
1|1|2|4|7
2|3|4|5|8
답변2
두 번째 테스트 데이터 세트에 GNU awk를 사용하세요.
BEGIN { FS = OFS = "|" }
# like the shell's shift function, returns the "former" first field
function shift( value, i) {
value = $1
for (i=1; i<NF; i++) $i = $(i+1)
NF--
return value
}
# return a string with a character repeated n times
# repeat("x", 5) ==> "xxxxx"
function repeat(char, n, str) {
str = sprintf("%*s", n, "")
gsub(/ /, char, str)
return str
}
FNR == 1 {fn++; nf[fn] = NF - 1}
{
key = shift()
data[fn][key] = $0
seen[key]
}
END {
for (key in seen) {
printf "%s", key
for (f=1; f<=fn; f++) {
if (key in data[f])
row = data[f][key]
else
row = repeat(FS, nf[f] - 1)
printf "%s%s", FS, row
}
print ""
}
}
그 다음에
gawk -f joiner.awk test{1,2,3}.txt
1|1|2|4|7
2|3|4|5|8
3|||6|9
4||||10