一般「一部を読みながら」を「並列」に変換/交換

一般「一部を読みながら」を「並列」に変換/交換

これを行う多くのスクリプトがあります。

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ここで、タイムスタンプはハッシュキーで、値はそのタイムスタンプを含むファイル名を含む匿名配列です。これは最適化であるため、タッチ機能は各ファイル名に対して1回ではなく、各タイムスタンプに対して1回だけ実行できます。

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} });
};

このスクリプトは日付::分析ファイル::タッチ文字列::脱出パールモジュール。

Debian では、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が変更され、再コミットされたときに明確にするためにコミットメッセージを編集しました。


2番目の試みは次のとおりです。最初はGit::生モジュールは私に与えられたリストを取得する方法を知りませんただ特定のコミットで変更されたファイルの名前。きっと方法があるはずなのにあきらめました。私はその内部をよく理解していませんgit

関連情報