여러 명령에 입력을 전달하고 출력을 비교합니다.

여러 명령에 입력을 전달하고 출력을 비교합니다.

여러 명령에 표준 입력을 전달하고 출력을 비교하려고 합니다. 나의 현재 시도는 비슷해 보이지만 그다지 효율적이지는 않습니다. 그리고 그것은 필요하지 않다고 생각하는 임시 파일에 의존합니다.

스크립트에서 수행하려는 작업의 예:

$ 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: $!";
}

노트:

  1. 유사한 코드는 delete $$_[1] for @cl배열에서 파일 핸들을 제거할 뿐만 아니라지금 닫아, 이를 가리키는 다른 참조가 없기 때문에 이는 와 같은 (적절한) 가비지 수집 언어와 일치하지 않습니다 javascript.

  2. 종료 상태는 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-*

관련 정보