做一个脚本来观察文件内部

Do a script to watch inside a file

我目前正在编写一个脚本来监视文件中是否有任何添加的行。 我当前的脚本是:

#!/bin/bash

GRAY_R='3[1;30m'
RESET='3[0m'
refreshtime="0.5"
filetowatch="/root/upgrade.log"

while [ -n "" ]; do
  case "" in
  -r | --refresh)
    isnumber='^[0-9]+([.][0-9]+)?$'
    if ! [[  =~ $isnumber ]] ; then
      echo "--refresh || -r need a number as argument (int or float)" >&2; exit 1
    else
      refreshtime=""
      shift
    fi;;

  -h | --help)
    echo "You can use -r or --refresh to set a refresh time (0.5s by default) or give a file as argument to watch this file (/root/upgrade.log by default)."
    exit;;

  *)
  if [ -f "" ]; then
  filetowatch=""
  else
    echo " is not a file, please use -h or --help to see help."
  fi;;
esac
shift
done

tput smcup 
while true
do
  clear
  cat ${filetowatch}
  echo
  echo -e "${GRAY_R}Press Q to quit    - - - - - - - - - - - - - - -  Refresh time ${refreshtime}s${RESET}"
  read -t ${refreshtime} -N 1 input
  if [[ $input = "q" ]] || [[ $input = "Q" ]]
  then
    echo
    clear
    break
  fi
done
tput rmcup

这工作得很好,正如我想要的,但有 1 个问题。 当我观看一个很长的文件(可能超过 30 行)时,它的一部分会滚出视图,我无法使用滚动条来阅读它,因为脚本使用 clear 然后 cat 一次又一次,干扰滚动。

所以,我想知道如何做 tail -f?

我想编写脚本,而不仅仅是使用 tail -f 来完成(我这样做是为了学习,否则我可以只使用 watch cat 而不是这个脚本)

对不起我的英语,谢谢你的帮助!

如果我理解正确,您正在尝试模拟命令tail -f

有一种快速而优雅的方法,使用 read,如 所建议:

# parse command line and get filename and timeout
...

# watch the file until told to quit
clear
while true; do
  # read line by line while there is something to read
  while IFS='' read -r line; do
    echo "$line"
  done
  # then wait for user input for the specified time
  read -t ${refreshtime} -N 1 input
  if [[ $input = "q" ]] || [[ $input = "Q" ]]; then
    break
  fi
  # then loop back to trying to read the file. The script "remembers"
  # where it left off in the file, because the file is opened by the
  # outer while loop, which is still running
done <"${filetowatch}"

在您的原始脚本中,底部有一个提示(按 Q 退出...)。让我们在这里添加。我们将使用一个技巧,这样我们就不必每次都使用 clear。只要您的提示字符串不超过一个终端行,这就会起作用:

  ...
  # then wait for user input for the specified time
  echo -ne "${GRAY_R}Press Q to quit - Refresh time ${refreshtime}s${RESET}" # -n means the cursor stays on this line
  read -t ${refreshtime} -N 1 input
  if [[ $input = "q" ]] || [[ $input = "Q" ]]; then
    break
  fi
  echo -ne '\r3[K' # go back to the beginning of the line and erase it
  # then loop back to trying to read the file
  ...

现在让我们添加一个边缘案例 - 如果您正在查看日志文件,它可能会不时被截断,因为日志文件通常会定期轮换。 read 解决方案将不起作用,因为它会继续尝试读取它停止的最后一个位置。

由于这个问题是出于学习目的,我假设您可以接受速度较慢且磁盘密集度更高的解决方案(并且不建议在生产中使用)。

执行此操作的一种方法是跟踪您在跟踪文件中已经看到的行数,并且仅打印新部分。当日志文件轮转时,您需要将“看到”的行数重置为零 - 您可能会丢失一些输出(在轮转之前添加到日志文件的行,但在您上次阅读文件)。

要打印文件的一部分,您可以使用 tail 本身 - 它可以选择从给定行 (-n +K) 开始打印文件。 如果(出于学习目的)您根本不想使用 tail,您也可以阅读每一行并打印您需要的内容(同样,速度较慢):

# parse command line and get filename and timeout
#...

# watch the file until told to quit

lines_seen=0
clear

while true; do
  lines_total=$(wc -l "${filetowatch} | cut -f 1 -d ' '")

  if ((lines_total < lines_seen)); then
    # log file was truncated
    lines_seen=0
  fi

  if ((lines_total > lines_seen)); then
    for (( i=0; ; i++ )); do
      # read the whole file
      if IFS='' read -r line; then
        # but only print newer lines
        if ((lines_seen <= i)); then
          echo "$line"
        fi
      else
        break
      fi
    done <${filetowatch}
    # update the number of lines printed (which may be different than lines_total meanwhile)
    lines_seen=$i
  fi

  echo -ne "${GRAY_R}Press Q to quit - Refresh time ${refreshtime}s - ${lines_seen} lines${RESET}"
  read -t ${refreshtime} -N 1 input
  echo -ne '\r3[K'
  if [[ $input = "q" ]] || [[ $input = "Q" ]]; then
    break
  fi
done