これを実行するスクリプトが多数あります:
command | while read something; do
a
long
list
of
commands
done
パイプを通して送られたすべてのコマンドを を使って実行する方法を考えたことがある人はいますかparallel
?このためにこの問題の解決策を探していますが、何かを探しています一般的なスクリプトに最小限の変更を加えるだけで済み、可能であればインストールparallel
されていない場合でも実行できます。
command
上記は、単一のコマンドに限定されず、ほぼすべてに当てはまります。a long list of commands
まったく異なる場合もあります。
たとえば、git リポジトリ内のチェックアウトされたファイルの変更日を最後に変更された日付に変更する次の 1 行のコードを検討してください。
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 つの例であり、単純な例です。
答え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 のいずれかで、独立したコマンドのリストを同時に実行し、各ストリームが前のタスクが終了するとすぐに新しいコマンドを開始するようにすることができます。ストリームは、末尾のコマンドを除いてビジー状態に保たれます。
基本的な方法は、すべて同じパイプから読み取る非同期シェルをいくつか起動することです。パイプは行バッファリングとアトミック読み取りを保証します (コマンドのファイルはリダイレクトで使用できます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
bash while read ループでgit log
、date
、 、 を複数回実行する代わりにtouch
、次の Perl スクリプトは の出力を取得しgit log --name-only HEAD
、コミット ログに記載されているファイルの最新のタイムスタンプを というハッシュに格納します%files
。存在しないファイル名は無視されます。
次に、タイムスタンプをハッシュ キーとして、そのタイムスタンプを持つファイル名を含む匿名配列の値を使用して、 と呼ばれる配列のハッシュ (「HoA」 - を参照man perldsc
)を構築します%times
。これは最適化であり、touch 関数をファイル名ごとに 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} });
};
このスクリプトでは、日付::解析、 ファイル::タッチ、 そして文字列::エスケープperl モジュール。
Debian では、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 が変更されて再度コミットされたことを明確にしました。それ以外は、ターミナルから直接コピーして貼り付けました)。
これは2回目の試みです。最初はGit::生モジュールですが、どのように取得すればリストが表示されるのか分かりませんでしたのみ特定のコミットで変更されたファイル名。方法はあると思いますが、諦めました。内部の仕組みをgit
十分に理解していないからです。