일반 "일부를 읽는 동안"을 "병렬"로 변환/교체

일반 "일부를 읽는 동안"을 "병렬"로 변환/교체

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

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 log기본적으로 과 는 모두 매우 느린 명령이므로 매우 느립니다 touch. 그러나 이것은 하나의 예일 뿐이고 간단한 예입니다.

답변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-treethen 을 실행한 git log다음 읽기 루프 동안 bash에서 여러 번 실행하는 date대신 다음 perl 스크립트는 touch출력을 가져와 커밋 로그에 언급된 파일의 최신 타임스탬프를 다음과 같은 git log --name-only HEAD해시에 저장합니다. %files존재하지 않는 파일 이름은 무시됩니다.

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

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

스크립트는 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여러 정크 파일( , 및 )이 포함된 file2git 저장소 의 사용 예 :

$ 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.

관련 정보