Как я могу заставить скрипт войти в отдельный файл столько раз, сколько он был выполнен?

11

Мне нужно написать скрипт, который записывает в файл, сколько раз этот скрипт был выполнен.

Как я могу это сделать?

Милен Григоров
источник

Ответы:

15

Я предполагаю, что вы хотите иметь один файл, countfileкоторый содержит только одно число, представляющее счетчик выполнения.

Вы можете прочитать этот счетчик в переменную оболочки, $counterнапример, используя одну из следующих строк:

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

Простые целочисленные добавления могут быть сделаны в самом Bash с использованием $(( EXPRESSION ))синтаксиса. Затем просто напишите результат обратно в наш countfile:

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

Вам, вероятно, следует также защитить свой сценарий для случая, который countfileеще не существует, и затем создать его, инициализированный со значением 1.

Все это может выглядеть так:

#!/bin/bash
if [[ -f countfile ]] ; then
    read counter < countfile
else
    counter=0
fi
echo "$(( counter + 1 ))" > countfile
Byte Commander
источник
2
... или вот так:echo $(( $(cat countfile 2>/dev/null || echo 0) + 1 )) > countfile
PerlDuck
1
Хорошо, похоже, это тоже работает @PerlDuck. Я боялся, что он может открыть файл для перезаписи до того, как его прочитают, но, очевидно, синтаксис замещения процесса, кажется, предотвращает это.
Byte Commander
Хорошая точка зрения. Я не совсем уверен, гарантируется ли, что он $(…)исполняется раньше всего (особенно перед >). Но я часто использую $(cat countfile 2>/dev/null || echo 0)часть, чтобы получить разумное значение по умолчанию в случае, если файл не существует. Мы могли бы добавить sponge:-), чтобы быть в безопасности.
PerlDuck
2
Этот ответ может быть улучшен путем включения этого кода подсчета в flockкоманду, чтобы предотвратить состояние гонки. См. Unix.stackexchange.com/a/409276
rrauenza
5

Просто дайте сценарию создать файл журнала, добавьте, например, строку в свой сценарий в конце:

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

Таким образом, вы можете отформатировать способ представления даты и времени самостоятельно, но если вы просто хотите использовать полную дату и время (и HH:MM:SSэто приемлемый формат для вас), вы также можете просто использовать:

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

Тогда вы могли бы сделать:

wc -l ~/script.log

Который подсчитывает символы новой строки и дает вам оценку количества строк в файле журнала. До этого вы можете видеть в файле журнала, даже когда он был выполнен. Чтобы адаптировать его к вашим потребностям, вы можете изменить пути и имена, используемые для регистрации. Я только что сделал пример, который сохраняет файл журнала в ~.

Так, например, вы хотите, чтобы скрипт добавил это количество к строке, которую вы добавили в конце вашего скрипта, вы можете сделать что-то вроде этого в начале вашего скрипта:

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"

И измените свою строку в конце скрипта на что-то, включая даже эту информацию:

echo "Script has been executed $count times at $(date +\%F-\%T)" >> ~/script.log
Videonauth
источник
5

Это решение использует тот же подход, что и ответ Byte Commander, но оно не опирается на арифметику оболочки или другие Bashisms.

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

Перенаправления потока

  1. дублировать стандартный поток ошибок (2) в другой файловый дескриптор (3),
  2. замените его (2) на перенаправление на /dev/null(для подавления сообщения об ошибке при последующем перенаправлении ввода readкоманды, если файл счетчика ожидаемо отсутствует),
  3. позже скопируйте исходный стандартный поток ошибок (теперь на 3) на место (2) и
  4. закройте копию стандартного потока ошибок (3).
Дэвид Фёрстер
источник
1

Другой подход

Отдельный файл счетчика имеет недостатки:

  • Требуется 4096 байт (или любой другой размер вашего блока) для каждого файла счетчика.
  • Вы должны найти имя файла в скрипте bash, а затем открыть файл, чтобы увидеть количество.
  • Нет блокировки файлов (в других ответах), поэтому возможно, что два человека обновят счетчик в одно и то же время (это называется условием гонки в комментариях под ответом Byte Commander).

Таким образом, этот ответ устраняет отдельный файл счетчика и помещает счет в сам скрипт bash!

  • Установка счетчика в самом скрипте bash позволяет вам увидеть в самом скрипте, сколько раз он был запущен.
  • Использование flockгарантирует, что на короткое время два пользователя не смогут запустить скрипт одновременно.
  • Поскольку имя файла счетчика не жестко запрограммировано, вам не нужно менять код для различных сценариев, вы можете просто получить его или скопировать и вставить из файла-заглушки / шаблона.

Код

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

Другой подход с использованием файла журнала

Подобно ответу Видеонавта, я написал здесь ответ из файла журнала: Bash-скрипт для ведения контрольного журнала / журнала файлов, к которым обращаются для регистрации каждый раз, когда полномочия root используются с geditили nautilus.

Уловка, однако, заключается в том, что вместо использования gksuсценария именуется gsuи вызывает pkexec«современный» способ использования sudo в графическом интерфейсе, поэтому мне сказали.

Еще одним преимуществом является то, что он не только говорит, что каждый раз с полномочиями root были использованы, geditно он записывает имя файла, который был отредактирован. Вот код

~/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

Содержимое файла журнала gsu-log-file-for-geditпосле нескольких правок:

__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
источник