これを行う多くのスクリプトがあります。
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 -0
NULに分割するには。
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-tree
thenを実行してから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
複数のジャンクファイル(、および)を含むfile2
gitリポジトリの使用例:
$ 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
。