Contar el resultado en una declaración find/exec

Contar el resultado en una declaración find/exec

Tengo algunos archivos Perl que debo modificar mediante una simple sedllamada. Básicamente, tengo que eliminar la primera línea de todos ellos. Actualmente, logré hacer esto:

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

Sin embargo, estoy usando esta línea en un guión y me gustaría que fuera un poco más... hablador. Por lo tanto, decidí hacer eco de un contador (en tiempo real), que mostraría cuántos archivos se han procesado hasta el momento.

Sé que el recuento de archivos Perl se puede recuperar a través de

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

Actualmente tengo este

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
}

Ahora, lo que me gustaría como resultado sería algo como esto:

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

Y el valor debería actualizarse automáticamente. Pero no veo cómo incrementar la countvariable con mi declaración find/exec. Básicamente, cada vez que sedtermine de trabajar en un archivo, debería incrementar la countvariable.

Respuesta1

Si tienes bash 4, considera usar globstar en su lugar. Te da globbing recursivo.

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

Esta solución funcionará en archivos con caracteres locos en sus nombres y evita una subcapa.

Pero si bash 4 no es una opción, puedes recrear esta solución usando 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 {} +

Sin embargo, esto está sujeto al ARG_MAX de su sistema (a diferencia de lo anterior), por lo que si la cantidad de archivos es muy grande, aún podría terminar con múltiples ejecuciones sobre subconjuntos de archivos.

Respuesta2

¿Qué tal esto?

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

Es más seguro usarlo sed -i~aquí. Sed guarda el archivo antiguo como file~.

Respuesta3

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 ""}'

información relacionada