여러 명령에 표준 입력을 전달하고 출력을 비교하려고 합니다. 나의 현재 시도는 비슷해 보이지만 그다지 효율적이지는 않습니다. 그리고 그것은 필요하지 않다고 생각하는 임시 파일에 의존합니다.
스크립트에서 수행하려는 작업의 예:
$ echo '
> Line 1
> Line B
> Line iii' | ./myscript.sh 'sed s/B/b/g' 'sed s/iii/III/' 'cat'
1:Line B 2:Line b
1:Line iii 3:Line III
지금까지 나는 이것을 가지고 있습니다 :
i=0
SOURCES=()
TARGETS=()
for c in "$@"; do
SOURCES+=(">($c > tmp-$i)")
TARGETS+=("tmp-$i")
i=$((i+1))
done
eval tee ${SOURCES[@]} >/dev/null <&0
comm ${TARGETS[@]}
문제는 다음과 같습니다
- 경쟁 조건이 있는 것 같습니다. 실행이 끝나면 comm tmp-0 tmp-1은 원하는 출력(다소)을 가지지만 스크립트에서 실행하면 출력이 정의되지 않은 것처럼 보입니다.
- 입력은 2개로 제한되어 있지만 최소한 3개(숫자 상관없음) 이상이 필요합니다.
- 이렇게 하면 추적하고 삭제해야 하는 임시 파일이 생성됩니다. 이상적인 솔루션은 리디렉션을 사용하는 것입니다.
제한사항은 다음과 같습니다.
- 아직 입력이 완료되지 않았을 수 있습니다. 특히 입력은 /dev/zero 또는 /dev/urandom과 같을 수 있으므로 입력을 파일에 복사하는 것만으로는 작동하지 않습니다.
- 명령에는 공백이 포함될 수 있으며 그 자체가 매우 복잡합니다.
- 행별로 순차적으로 비교하고 싶습니다.
이것을 어떻게 구현할 수 있는지 아시나요? 나는 기본적으로 echo $input | tee >(A >?) >(B >?) >(C >?) ?(compare-all-files)
그러한 구문이 존재한다면 비슷한 것을 원합니다 .
답변1
허용되는 답변은 perl
.perl
ytee
이 방법을 사용하면 이 답변 끝에 있는 스크립트는 다음과 같습니다.
ytee command filter1 filter2 filter3 ...
같을 것이다
command <(filter1) <(filter2) <(filter3) ...
표준 입력은 마치 , , ...와 filter1
병렬 filter2
로 파이프됩니다.filter3
tee >(filter1) >(filter2) >(filter3) ...
예:
echo 'Line 1
Line B
Line iii' | ytee 'paste' 'sed s/B/b/g | nl' 'sed s/iii/III/ | nl'
1 Line 1 1 Line 1
2 Line b 2 Line B
3 Line iii 3 Line III
이는 또한 매우 유사한 두 가지 질문에 대한 답변이기도 합니다.여기그리고여기.
이티:
#! /usr/bin/perl
# usage: ytee [-r irs] { command | - } [filter ..]
use strict;
if($ARGV[0] =~ /^-r(.+)?/){ shift; $/ = eval($1 // shift); die $@ if $@ }
elsif(! -t STDIN){ $/ = \0x8000 }
my $cmd = shift;
my @cl;
for(@ARGV){
use IPC::Open2;
my $pid = open2 my $from, my $to, $_;
push @cl, [$from, $to, $pid];
}
defined(my $pid = fork) or die "fork: $!";
if($pid){
delete $$_[0] for @cl;
$SIG{PIPE} = 'IGNORE';
my ($s, $n);
while(<STDIN>){
for my $c (@cl){
next unless exists $$c[1];
syswrite($$c[1], $_) ? $n++ : delete $$c[1]
}
last unless $n;
}
delete $$_[1] for @cl;
while((my $p = wait) > 0){ $s += !!$? << ($p != $pid) }
exit $s;
}
delete $$_[1] for @cl;
if($cmd eq '-'){
my $n; do {
$n = 0; for my $c (@cl){
next unless exists $$c[0];
if(my $d = readline $$c[0]){ print $d; $n++ }
else{ delete $$c[0] }
}
} while $n;
}else{
exec join ' ', $cmd, map {
use Fcntl;
fcntl $$_[0], F_SETFD, fcntl($$_[0], F_GETFD, 0) & ~FD_CLOEXEC;
'/dev/fd/'.fileno $$_[0]
} @cl;
die "exec $cmd: $!";
}
노트:
유사한 코드는
delete $$_[1] for @cl
배열에서 파일 핸들을 제거할 뿐만 아니라지금 닫아, 이를 가리키는 다른 참조가 없기 때문에 이는 와 같은 (적절한) 가비지 수집 언어와 일치하지 않습니다javascript
.종료 상태는
ytee
명령의 종료 상태를 반영합니다.그리고필터; 이는 변경/단순화될 수 있습니다.
답변2
이것은 더 간단합니다:
#!bash
if [[ -t 0 ]]; then
echo "Error: you must pipe data into this script"
exit 1
fi
input=$(cat)
commands=$( "$@" )
outputs=()
for cmd in "${commands[@]}"; do
echo "calling: $cmd"
outputs+=( "$( $cmd <<<"$input" )" )
done
# now, do stuff with "${outputs[0]}", "${outputs[1]}", etc
이것은 테스트되지 않았습니다. 이 outputs+=...
라인은 특히 취약합니다.http://mywiki.wooledge.org/BashFAQ/050
답변3
줄이 RAM 크기보다 길면 이 작업이 실패합니다.
#!/bin/bash
commands=('sed s/8/b/g' 'sed s/7/III/' cat)
parallel 'rm -f fifo-{#};mkfifo fifo-{#}' ::: "${commands[@]}"
cat input |
parallel -j0 --tee --pipe 'eval {} > fifo-{#}' ::: "${commands[@]}" &
perl -e 'for(@ARGV){ open($in{$_},"<",$_) }
do{
@in = map { $f=$in{$_}; scalar <$f> } @ARGV;
print grep { $in[0] ne $_ } @in;
} while (not grep { eof($in{$_}) } @ARGV)' fifo-*