보편적인 '무언가를 읽는 동안'을 '병렬'로 변환/교체

보편적인 '무언가를 읽는 동안'을 '병렬'로 변환/교체

이 작업을 수행하는 수많은 스크립트가 있습니다.

command | while read something; do
    a
    long
    list
    of
    commands
done

를 사용하여 파이프를 통해 공급된 모든 명령을 실행하는 방법을 생각해 본 사람이 있습니까 parallel? 있다애드 혹이 문제에 대한 해결책이 있는데 뭔가를 찾고 있어요일반적인parallel내 스크립트에 최소한의 변경만 필요하며 가능하다면 설치되지 않은 경우에도 실행할 수 있습니다 .

command위의 내용은 단일 명령에 국한되지 않고 거의 모든 것이 될 수 있습니다. a long list of commands완전히 다를 수도 있습니다.

예를 들어, git 저장소에서 체크아웃된 파일의 수정 날짜를 마지막으로 변경된 날짜로 변경하는 다음 라이너를 고려해보세요.

git ls-tree -r --name-only HEAD | 
while read filename; do
   unixtime=$(git log -1 --format="%at" -- "${filename}");
   touchtime=$(date -d @$unixtime +'%Y%m%d%H%M.%S');
   touch -t ${touchtime} "${filename}";
done

기본적으로 git logtouch둘 다 매우 느린 명령이기 때문에 매우 느립니다. 그러나 그것은 하나의 예일 뿐이고 단순한 예입니다.

답변1

나는 bash 함수를 사용하여 다음과 같이 호출합니다.

myfunc() {
   filename="$1"
   unixtime=$(git log -1 --format="%at" -- "${filename}");
   touchtime=$(date -d @$unixtime +'%Y%m%d%H%M.%S');
   touch -t ${touchtime} "${filename}";
}
export -f myfunc

git ls-tree -r --name-only HEAD | parallel myfunc

parallel -0NUL로 분할하려는 경우 사용합니다 .

GNU Parallel을 설치하지 않고 위의 명령을 실행하려면 다음을 사용할 수 있습니다.

parallel --embed > myscript.sh

그런 다음 위의 내용을 에 추가합니다 myscript.sh.

답변2

bash 또는 ksh를 사용하여 독립적인 명령 목록을 동시에 실행할 수 있으므로 각 스트림은 이전 작업이 종료되자마자 새 명령을 시작합니다. 스트림은 tail-end 명령을 제외하고 계속 사용 중입니다.

기본 방법은 동일한 파이프에서 모두 읽는 여러 비동기 셸을 시작하는 것입니다. 파이프는 라인 버퍼링 및 원자성 읽기를 보장합니다(명령 파일은 리디렉션과 함께 사용할 수 있지만 cat file |리디렉션으로는 사용할 수 없음).

명령은 임의의 셸 단일 라이너(스트림을 소유하는 셸에 대한 올바른 구문)일 수 있지만 스트림에 대한 명령 할당은 임의적이므로 이전 명령의 결과에 의존할 수 없습니다. 복잡한 명령은 인수가 포함된 간단한 명령으로 호출할 수 있도록 외부 스크립트로 설정하는 것이 가장 좋습니다.

이는 3개 스트림에서 6개 작업을 테스트 실행한 것으로, 작업의 중복을 보여줍니다. (노트북에서도 80개 스트림의 240개 작업에 대한 스트레스 테스트를 실시했습니다.)

Time now 23:53:47.328735254
Sleep until 00 seconds to make debug easier.
Starting 3 Streams
23:54:00.040   Shell   1 Job   1 Go    sleep 5
23:54:00.237   Shell   2 Job   2 Go    sleep 13
23:54:00.440   Shell   3 Job   3 Go    sleep 14
Started all Streams
23:54:05.048   Shell   1 Job   1   End sleep 5
23:54:05.059   Shell   1 Job   4 Go    sleep 3
23:54:08.069   Shell   1 Job   4   End sleep 3
23:54:08.080   Shell   1 Job   5 Go    sleep 13
23:54:13.245   Shell   2 Job   2   End sleep 13
23:54:13.255   Shell   2 Job   6 Go    sleep 3
23:54:14.449   Shell   3 Job   3   End sleep 14
23:54:16.264   Shell   2 Job   6   End sleep 3
23:54:21.089   Shell   1 Job   5   End sleep 13
All Streams Ended

이는 해당 작업에 대한 디버그를 제공하는 프록시 스크립트입니다.

#! /bin/bash

#.. jobProxy.
#.. arg 1: Job number.
#.. arg 2: Sleep time.
#.. idStream: Exported into the Stream's shell.

    fmt='%.12s   Shell %3d Job %3d %s sleep %s\n'
    printf "${fmt}" $( date '+%T.%N' ) "${idStream}" "${1}" "Go   " "${2}"
    sleep "${2}"
    printf "${fmt}" $( date '+%T.%N' ) "${idStream}" "${1}" "  End" "${2}"

스트림 관리 스크립트입니다. 프록시를 실행하기 위한 작업 명령을 생성하고 백그라운드 셸을 시작합니다.

#! /bin/bash

makeJobs () {

    typeset nJobs="${1}"

    typeset Awk='
BEGIN { srand( Seed % 10000000); fmt = "./jobProxy %s %3d\n"; }
{ printf (fmt, $1, 2 + int (14 * rand())); }
'
    seq 1 "${nJobs}" | awk -v Seed=$( date "+%N$$" ) "${Awk}"
}

runStreams () {

    typeset n nStreams="${1}"

    echo "Starting ${nStreams} Streams"
    for (( n = 1; n <= nStreams; ++n )); do
        idStream="${n}" bash -s &
        sleep 0.20
    done
    echo "Started all Streams"

    wait
    echo "All Streams Ended"
}

## Script Body Starts Here.

    date '+Time now %T.%N'
    echo 'Sleep until 00 seconds to make debug easier.'
    sleep $( date '+%S.%N' | awk '{ print 60 - $1; }' )

    makeJobs 6 | runStreams 3

답변3

git ls-treebash 동안 읽기 루프에서 and then git log, date및 여러 번 실행하는 대신 touch다음 Perl 스크립트는 의 출력을 가져 git log --name-only HEAD와서 커밋 로그에 언급된 모든 파일에 대한 가장 최근 타임스탬프를 이라는 해시에 저장합니다 %files. 존재하지 않는 파일 이름은 무시됩니다.

그런 다음 타임스탬프를 해시 키로 사용하고 값은 해당 타임스탬프가 있는 파일 이름을 포함하는 익명 배열로 호출되는 배열 해시("HoA" - 참조 man perldsc)를 구축합니다. %times이는 각 파일 이름에 대해 한 번이 아니라 각 타임스탬프에 대해 터치 기능을 한 번만 실행하면 되도록 최적화된 것입니다.

커밋 ID, 커밋 메시지, 작성자 이름 및 git log의 출력에서 ​​빈 줄은 무시됩니다.

스크립트는 unqqbackslash()다음의 함수를 사용합니다.문자열::이스케이프git log포함된 탭, 개행, 큰따옴표 등(즉, 백슬래시 이스케이프 코드/문자가 있는 큰따옴표 문자열)이 포함된 파일 이름을 인쇄하는 방식을 올바르게 처리하려면 각 파일 이름에 대해

나는 그것이 적어도 bash 루프보다 수십 배 더 빠르게 실행될 것으로 기대합니다.

#!/usr/bin/perl

use strict;
use Date::Parse;
use File::Touch;
use String::Escape qw(unqqbackslash);

my %files = ();
my %times = ();
my $t;

while (<>) {
  chomp;
  next if (m/^$|^\s+|^Author: |^commit /);

  if (s/^Date:\s+//) {
    $t = str2time($_);

  } else {
    my $f = unqqbackslash($_);
    next unless -e $f;   # don't create file if it doesn't exist

    if (!defined($files{$f}) || $files{$f} < $t) {
      $files{$f} = $t;
    }

  };
};

# build %files HoA with timestamps containing the
# files modified at that time.
foreach my $f (sort keys %files) {
  push @{ $times{$files{$f}} }, $f;
}

# now touch the files
foreach my $t (keys %times) {
  my $tch = File::Touch->new(mtime_only => 1, time => $t);
  $tch->touch(@{ $times{$t} });
};

스크립트는날짜::분석, 파일::터치, 그리고문자열::이스케이프펄 모듈.

데비안에서는 apt install libtimedate-perl libfile-touch-perl libstring-escape-perl. 다른 배포판에도 패키지가 있을 수 있습니다. 그렇지 않은 경우에는 cpan.

file몇 가지 정크 파일( , 및 ) 이 있는 git 저장소의 사용 예 file2:

$ git log --date=format:'%Y-%m-%d %H:%M:%S' --pretty='%H  %ad %s' file*
d10c313abb71876cfa8ad420b10f166543ba1402  2021-06-16 14:49:24 updated file2
61799d2c956db37bf56b228da28038841c5cd07d  2021-06-16 13:38:58 added file1
                                                              & file2

$ touch file*
$ ls -l file*
-rw-r--r-- 1 cas cas  5 Jun 16 19:23 file1
-rw-r--r-- 1 cas cas 29 Jun 16 19:23 file2

$ git  log  --name-only HEAD file*  | ./process-git-log.pl 
$ ls -l file*
-rw-r--r-- 1 cas cas  5 Jun 16 13:38 file1
-rw-r--r-- 1 cas cas 29 Jun 16 14:49 file2

(매우 약간 위조됨 - 두 파일이 처음 커밋된 다음 file2가 변경되어 다시 커밋되었을 때 명확하게 하기 위해 커밋 메시지를 편집했습니다. 그 외에는 터미널에서 직접 복사하여 붙여넣었습니다.)


이번이 두 번째 시도입니다. 원래는힘내::원시모듈을 찾았지만 나에게 목록을 제공하는 방법을 알 수 없었습니다.오직특정 커밋에서 수정된 파일 이름. 분명 방법이 있을 텐데 포기했어요. 나는 단지 내부를 git충분히 알지 못합니다.

관련 정보