Zählergebnis in einer Find/Exec-Anweisung

Zählergebnis in einer Find/Exec-Anweisung

Ich habe einige Perl-Dateien, die ich durch einen einfachen Aufruf ändern muss sed. Im Wesentlichen muss ich bei allen die erste Zeile entfernen. Bisher ist mir Folgendes gelungen:

find <PATH> -type f -name "*.pl" -exec sed -i '1d' {} \;

Ich verwende diese Zeile jedoch in einem Skript und möchte, dass sie etwas ... gesprächiger ist. Daher habe ich beschlossen, einen Zähler (in Echtzeit) auszugeben, der anzeigt, wie viele Dateien bisher verarbeitet wurden.

Ich weiß, dass die Anzahl der Perl-Dateien abgerufen werden kann durch

PERL_FILE_COUNT=$(find <PATH> -name "*.pl" | wc -l)

Derzeit habe ich dies

remove_first_line()
{
    count=0
    echo -ne "Removing first line of Perl files ..."
    find <PATH> -type f -name "*.pl" -exec sed -i '1d' {} \; >/dev/null 2>&1

    if [ $? -eq 0 ]
        then echo "OK"
        then echo "FAILED"
    fi
}

Als Ausgabe hätte ich jetzt gerne etwa Folgendes:

"Removing first line of Perl files ... 1/209209"

Und der Wert sollte automatisch aktualisiert werden. Aber ich weiß nicht, wie ich die countVariable mit meiner Find/Exec-Anweisung erhöhen kann. Grundsätzlich sedsollte die countVariable jedes Mal erhöht werden, wenn die Bearbeitung einer Datei abgeschlossen ist.

Antwort1

Wenn Sie Bash 4 haben, sollten Sie stattdessen Globstar verwenden. Damit erhalten Sie rekursives Globbing.

shopt -s globstar
perlz=( **/*.pl ) # */ Hack to fix syntax highlighting
totes="${#perlz[@]}"
i=0
for file in "${perlz[@]}"; do
    printf 'Removing first line of Perl files … %d/%d\r' $((++i)) $totes
    ed -s "$file" <<< $'1d\nw' # You can use `sed` if you want to, but ed is an actual file editor
done
echo # print a final newline

Diese Lösung funktioniert bei Dateien mit verrückten Zeichen im Namen und vermeidet eine Subshell.

Wenn Bash 4 jedoch keine Option ist, können Sie diese Lösung mit folgendem Verfahren neu erstellen find -exec +:

find . -name '*.pl' -exec bash -c 'totes=$#
  i=0
  for file; do
    printf "Removing first line of Perl files … %d/%d\r" $((++i)) $totes
    ed -s "$file" <<< $'\''1d\nw'\'' # Avoid these leaning toothpicks by putting this
                                     # script in a file.
  done
  echo # print a final newline
' bash {} +

Dies unterliegt jedoch (im Gegensatz zum Obigen) dem ARG_MAX Ihres Systems, sodass bei einer sehr großen Anzahl von Dateien möglicherweise trotzdem mehrere Durchläufe über Teilmengen der Dateien erforderlich sind.

Antwort2

Wie wäre es damit?

#!/bin/bash

failed=0

find . -type f -name "*.pl" | while read file; do
   if [ -e "$file" ] && [ -r "$file" ]; then
     sed -i~ "1d" "$file"
     if [ $? != 0 ]; then
        echo "sed returns $? on ($file)"
        (( failed++  ))
     fi
   else
      echo "warning ($file) not exists, or not readable"
      (( failed++ ))
   fi
done

echo "failed execution: $failed"

Die Verwendung hier ist sicherer sed -i~. Sed speichert die alte Datei als file~.

Antwort3

GNUly:

find . -type f -name '*.pl' -size +0c -print0 > list &&
  count=$(grep -cz . < list) &&
  stdbuf -oL xargs < list -r0 sed -i -e '1{w /dev/stdout' -e 'd;}' |
    awk -v c="$count" '{printf "processed file %d/%d\r", NR, c}
                       END{print ""}'

verwandte Informationen