¿Cómo puedo hacer que un script inicie sesión en un archivo separado la cantidad de veces que se ha ejecutado?

11

Necesito escribir un script que escriba en un archivo cuántas veces se ha ejecutado este script.

¿Cómo puedo hacer eso?

Milen Grigorov
fuente

Respuestas:

15

Supongo que desea tener un solo archivo countfileque solo contenga un único número que represente el contador de ejecución.

Puede leer este contador en una variable de shell, $counterpor ejemplo, usando una de estas líneas:

  • read counter < countfile
  • counter=$(cat countfile)

Las adiciones enteras simples se pueden hacer en Bash usando la $(( EXPRESSION ))sintaxis. Luego simplemente escriba el resultado en nuestro countfile:

echo "$(( counter + 1 ))" > countfile

Probablemente también debería proteger su script para el caso que countfileaún no existe y crear uno inicializado con el valor 1 en ese momento.

Todo podría verse así:

#!/bin/bash
if [[ -f countfile ]] ; then
    read counter < countfile
else
    counter=0
fi
echo "$(( counter + 1 ))" > countfile
Byte Commander
fuente
2
... o así:echo $(( $(cat countfile 2>/dev/null || echo 0) + 1 )) > countfile
PerlDuck
1
Bien, eso parece funcionar también @PerlDuck. Tenía miedo de que pudiera abrir el archivo para sobrescribirlo antes de que se leyera, pero aparentemente la sintaxis de sustitución del proceso parece evitar eso.
Byte Commander
Buen punto. No estoy completamente seguro de si está garantizado que $(…)se ejecute antes que nada (especialmente antes del >). Pero a menudo uso la $(cat countfile 2>/dev/null || echo 0)parte para obtener un valor predeterminado razonable en caso de que el archivo no exista. Podríamos agregar un sponge:-) para estar seguro.
PerlDuck
2
Esta respuesta se mejoraría envolviendo ese código de conteo en un flockcomando para evitar condiciones de carrera. Ver unix.stackexchange.com/a/409276
rrauenza
5

Simplemente deje que el script cree un archivo de registro, agregue, por ejemplo, una línea en su script al final:

echo "Script has been executed at $(date +\%Y-\%m-\%d) $(date +\%H-\%M-\%S)" >> ~/script.log

De esta forma, puede formatear la fecha y la hora usted mismo, pero si simplemente desea ir con una fecha y hora completas (y HH:MM:SSes un formato aceptable para usted), también puede simplemente usar:

echo "Script has been executed at $(date +\%F-\%T)" >> ~/script.log

Entonces podrías hacer:

wc -l ~/script.log

Que cuenta los caracteres de nueva línea y le da una estimación de cuántas líneas hay dentro del archivo de registro. Hasta eso puede ver dentro del archivo de registro incluso cuando se ejecutó. Para adaptarlo a sus necesidades, puede cambiar las rutas y los nombres utilizados para iniciar sesión. Acabo de hacer un ejemplo aquí que guarda el archivo de registro ~.

Entonces, por ejemplo, si desea que el script agregue este conteo a la línea que agregó al final de su script, podría hacer algo como esto al comienzo de su script:

count=$(( $(wc -l ~/script.log | awk '{print $1}') + 1 ))
# the next line can be simply skipped if you not want an output to std_out
echo "Script execution number: $count"

Y cambie su línea al final del guión a algo que incluya incluso esa información:

echo "Script has been executed $count times at $(date +\%F-\%T)" >> ~/script.log
Videonauth
fuente
5

Esta solución utiliza el mismo enfoque que la respuesta de Byte Commander, pero no se basa en aritmética de shell u otros Bashismos.

exec 2>&3 2>/dev/null
read counter < counter.txt || counter=0
exec 3>&2 3>&-
expr "$counter" + 1 > counter.txt

Las redirecciones de flujo

  1. duplicar el flujo de error estándar (2) a un descriptor de archivo diferente (3),
  2. reemplácelo (2) con una redirección a /dev/null(para suprimir el mensaje de error en la redirección posterior de la entrada del readcomando si el archivo del contador falta)
  3. luego duplique el flujo de error estándar original (ahora en 3) nuevamente en su lugar (2)
  4. cierre la copia del flujo de error estándar (3).
David Foerster
fuente
1

Un enfoque diferente

Un archivo de contador separado tiene desventajas:

  • Se necesitan 4096 bytes (o el tamaño de su bloque) para cada archivo de contador.
  • Debe buscar el nombre del archivo en el script bash y luego abrir el archivo para ver el recuento.
  • No hay bloqueo de archivos (en otras respuestas), por lo que es posible que dos personas actualicen el contador exactamente al mismo tiempo (llamado condición de carrera en los comentarios bajo la respuesta de Byte Commander).

¡Entonces esta respuesta elimina un archivo de contador separado y coloca el conteo en el script bash mismo!

  • Poner el contador en el script bash en sí le permite ver dentro de su script cuántas veces se ha ejecutado.
  • El uso flockgarantiza que por un breve momento no es posible que dos usuarios ejecuten el script al mismo tiempo.
  • Debido a que el nombre del archivo del contador no está codificado, no necesita cambiar el código para diferentes scripts, simplemente puede obtenerlo o copiarlo y pegarlo desde un archivo stub / boilerplate.

El código

#!/bin/bash

# NAME: run-count.sh
# PATH: $HOME/bin
# DESC: Written for AU Q&A: /ubuntu/988032/how-can-i-cause-a-script-to-log-in-a-separate-file-the-number-of-times-it-has-be

# DATE: Mar 16, 2018.

# This script run count: 0

# ======== FROM HERE DOWN CAN GO INTO FILE INCLUDED WITH SOURCE COMMAND =======

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
#     This is useful boilerplate code for shell scripts.  Put it at the top  of
#     the  shell script you want to lock and it'll automatically lock itself on
#     the first run.  If the env var $FLOCKER is not set to  the  shell  script
#     that  is being run, then execute flock and grab an exclusive non-blocking
#     lock (using the script itself as the lock file) before re-execing  itself
#     with  the right arguments.  It also sets the FLOCKER env var to the right
#     value so it doesn't run again.

# Read this script with entries separated newline " " into array
mapfile -t ScriptArr < "$0"

# Build search string that cannot be named
SearchStr="This script"
SearchStr=$SearchStr" run count: "

# Find our search string in array and increment count
for i in ${!ScriptArr[@]}; do
    if [[ ${ScriptArr[i]} = *"$SearchStr"* ]]; then
        OldCnt=$( echo ${ScriptArr[i]} | cut -d':' -f2 )
        NewCnt=$(( $OldCnt + 1 ))
        ScriptArr[i]=$SearchStr$NewCnt
        break
    fi
done

# Rewrite our script to disk with new run count
# BONUS: Date of script after writing will be last run time
printf "%s\n" "${ScriptArr[@]}" > "$0"

# ========= FROM HERE UP CAN GO INTO FILE INCLUDED WITH SOURCE COMMAND ========

# Now we return you to your original programming....

exit 0

Otro enfoque utilizando un archivo de registro

Similar a la respuesta de Videonauth, escribí una respuesta de archivo de registro aquí: secuencia de comandos Bash para mantener el seguimiento / registro de auditoría de los archivos accedidos para registrar cada vez que se usaban poderes de raíz con gedito nautilus.

Sin embargo, el problema es que en lugar de usar gksuel script se nombra gsue invoca pkexecla forma "moderna" de usar sudo en la GUI, por lo que me han dicho.

Otra ventaja es que no solo dice cada vez que se usaron poderes de raíz, geditsino que registra el nombre del archivo que se editó. Aquí está el código.

~/bin/gsu:

#!/bin/bash

# Usage: gsu gedit file1 file2...
#  -OR-  gsu natuilus /dirname

# & is used to spawn process and get prompt back ASAP
# > /dev/null is used to send gtk warnings into dumpster

COMMAND="$1" # extract gedit or nautilus

pkexec "$COMMAND" "${@:2}"

log-file "${@:2}" gsu-log-file-for-"$COMMAND"

/usr/local/bin/log-file:

#! /bin/bash

# NAME: log-file
# PATH: /usr/local/bin
# DESC: Update audit trail/log file with passed parameters.
# CALL: log-file FileName LogFileName
# DATE: Created Nov 18, 2016.
# NOTE: Primarily called from ~/bin/gsu

ABSOLUTE_NAME=$(realpath "$1")
TIME_STAMP=$(date +"%D - %T")
LOG_FILE="$2"

# Does log file need to be created?
if [ ! -f "$LOG_FILE" ]; then
    touch "$LOG_FILE"
    echo "__Date__ - __Time__ - ______File Name______" >> "$LOG_FILE"
    #     MM/DD/YY - hh:mm:ss - "a/b/c/FileName"
fi

echo "$TIME_STAMP" - '"'"$ABSOLUTE_NAME"'"' >> "$LOG_FILE"

exit 0

Contenido del archivo de registro gsu-log-file-for-geditdespués de algunas ediciones:

__Date__ - __Time__ - ______File Name______
11/18/16 - 19:07:54 - "/etc/default/grub"
11/18/16 - 19:08:34 - "/home/rick/bin/gsu"
11/18/16 - 19:09:26 - "/home/rick/bin/gsu"
WinEunuuchs2Unix
fuente