通用的“while read some”到“parallel”轉換/替換

通用的“while read some”到“parallel”轉換/替換

我有大量的腳本可以做到這一點:

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 -0如果您想在 NUL 上拆分,請使用。

如果你想在不安裝 GNU Parallel 的情況下執行上面的程式碼,你可以使用:

parallel --embed > myscript.sh

然後將以上內容附加到myscript.sh.

答案2

可以讓 bash 或 ksh 同時執行一系列獨立命令,以便每個流在其前一個任務退出後立即開始一個新命令。除了尾端命令之外,流保持忙碌狀態。

基本方法是啟動多個非同步 shell,它們都從同一管道讀取:管道保證行緩衝和原子讀取(可以使用命令文件,cat file |但不能透過重定向來使用)。

命令可以是任何 shell 單行命令(使用擁有流的 shell 的正確語法),但不能依賴先前命令的結果,因為命令到流的分配是任意的。複雜的命令最好設定為外部腳本,這樣它們就可以作為帶有參數的簡單命令來呼叫。

這是三個流上的六個作業的測試運行,說明了作業的重疊。 (我還在我的筆記型電腦上對 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}"

這是流管理腳本。它會建立作業命令來運行代理,並啟動後台 shell。

#! /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

以下 perl 腳本不是在 bash while read 循環中多次運行git ls-treethen git logdate和 ,而是touch獲取 的輸出git log --name-only HEAD並將提交日誌中提到的任何文件的最新時間戳存儲在名為 的哈希中%files。它會忽略不存在的檔案名稱。

然後,它會建立一個名為 的陣列雜湊(「HoA」 - 請參閱參考資料man perldsc%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} });
};

該腳本使用日期::解析, 文件::觸摸, 和字串::轉義Perl 模組。

在 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 被更改並再次提交。除此之外,它是直接從我的終端複製貼上的)。


這是我的第二次嘗試:我最初嘗試使用Git::原始模組,但不知道如何獲得給我一個列表僅有的在特定提交中修改的檔案名稱。我確信有辦法,但我已經放棄了。我只是不太了解其內部原理git

相關內容