AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / server / 问题 / 103501
Accepted
BlueMark
BlueMark
Asked: 2010-01-18 03:17:57 +0800 CST2010-01-18 03:17:57 +0800 CST 2010-01-18 03:17:57 +0800 CST

如何完全记录所有 bash 脚本操作?

  • 772

从我的脚本输出中,我想捕获所有带有错误消息的日志数据并将它们全部重定向到日志文件。

我有如下脚本:

#!/bin/bash
(
echo " `date` : part 1 - start "
ssh -f [email protected] 'bash /www/htdocs/server.com/scripts/part1.sh logout exit'
echo " `date` : sleep 120"
sleep 120
echo " `date` : part 2 - start"
ssh [email protected] 'bash /www/htdocs/server.com/scripts/part2.sh logout exit'
echo " `date` : part 3 - start"
ssh [email protected] 'bash /www/htdocs/server.com/scripts/part3.sh logout exit'
echo " `date` : END"
) | tee -a /home/scripts/cron/logs

我想查看文件中的所有操作/home/scripts/cron/logs

但我只看到我在echo命令之后放置的内容。

如何在日志中检查该SSH命令是否成功?

我需要收集所有日志。我需要它来监视脚本中每个命令的结果,以便更好地分析脚本失败时发生的情况。

linux scripting log-files bash ssh
  • 9 9 个回答
  • 355240 Views

9 个回答

  • Voted
  1. Best Answer
    nicerobot
    2010-01-18T09:54:16+08:002010-01-18T09:54:16+08:00

    我通常在每个脚本的开头放置类似于以下内容的内容(特别是如果它将作为守护程序运行):

    #!/bin/bash
    exec 3>&1 4>&2
    trap 'exec 2>&4 1>&3' 0 1 2 3
    exec 1>log.out 2>&1
    # Everything below will go to the file 'log.out':
    

    解释:

    1. exec 3>&1 4>&2

      保存文件描述符,以便它们可以恢复到重定向之前的任何内容,或者使用它们自己输出到以下重定向之前的任何内容。

    2. trap 'exec 2>&4 1>&3' 0 1 2 3

      恢复特定信号的文件描述符。通常不需要,因为它们应该在子 shell 退出时恢复。

    3. exec 1>log.out 2>&1

      重定向stdout到文件,log.out然后重定向stderr到stdout. 请注意,当您希望它们转到同一个文件时,顺序很重要。stdout 必须在被重定向到之前stderr被重定向stdout。

    从那时起,要在控制台上查看输出(也许),您可以简单地重定向到&3. 例如,

    echo "$(date) : part 1 - start" >&3
    

    在执行上面的第 3 行之前,将转到指定的任何地方stdout,大概是控制台。

    • 128
  2. Alex Holst
    2010-01-18T10:02:40+08:002010-01-18T10:02:40+08:00

    当我阅读您的问题时,您不想记录输出,而是记录整个命令序列,在这种情况下,其他答案对您没有帮助。

    使用 -x 调用 shell 脚本以输出所有内容:

    sh -x foo.sh

    登录到您想要的文件:

    sh -x foo.sh >> /home/scripts/cron/logs

    • 27
  3. Christian
    2010-01-18T04:24:12+08:002010-01-18T04:24:12+08:00

    要将 ssh 输出到您的日志文件,您必须重定向stderr到stdout. 2>&1您可以通过在 bash 脚本之后附加来做到这一点。

    它应该是这样的:

    #!/bin/bash
    (
    ...
    ) 2>&1 | tee ...
    

    当这没有以正确的顺序显示消息时,尝试添加另一个子shell:

    #!/bin/bash
    ((
    ...
    ) 2>&1) | tee ...
    
    • 23
  4. Amandasaurus
    2010-01-26T03:38:44+08:002010-01-26T03:38:44+08:00

    在 bash 中,您可以放置set -x​​,然后它会打印出它执行的每个命令(以及 bash 变量)。您可以使用 将其关闭set +x。

    如果你想偏执,你可以set -o errexit输入你的脚本。这意味着如果一个命令返回非零退出代码,脚本将失败并停止,这是 Unix 标准方式来表示出现问题。

    如果你想获得更好的日志,你应该查看tsdebian moreutils/ubuntu 包。它将在每一行前面加上一个时间戳并将其打印出来。所以你可以看到事情发生的时间。

    • 12
  5. dragon951
    2019-06-03T00:45:08+08:002019-06-03T00:45:08+08:00

    按照其他人的说法,套装手册是一个很好的资源。我放:

    #!/usr/bin/env bash
    exec 1> command.log 2>&1
    set -x
    

    在脚本的顶部,我希望继续,或者set -ex如果它应该在错误时退出。

    • 4
  6. Andry
    2020-02-07T02:04:01+08:002020-02-07T02:04:01+08:00

    我发现@nicerobot (如何完全记录所有 bash 脚本操作?)答案可能不足以将所有输出完全重定向到控制台。一些输出仍然可能丢失。

    完整的重定向如下所示:

    #!/bin/bash
    
    # some basic initialization steps including `NEST_LVL` and `SCRIPTS_LOGS_ROOT variables...
    source ".../__myinit__.sh"
    
    # no local logging if nested call
    (( ! IMPL_MODE && ! NEST_LVL )) && {
      export IMPL_MODE=1
      exec 3>&1 4>&2
      trap 'exec 2>&4 1>&3' EXIT HUP INT QUIT RETURN
    
      [[ ! -e "${SCRIPTS_LOGS_ROOT}/.log" ]] && mkdir "${SCRIPTS_LOGS_ROOT}/.log"
    
      # RANDOM instead of milliseconds
      case $BASH_VERSION in
        # < 4.2
        [123].* | 4.[01] | 4.0* | 4.1[^0-9]*)
          LOG_FILE_NAME_SUFFIX=$(date "+%Y'%m'%d_%H'%M'%S''")$(( RANDOM % 1000 ))
          ;;
        # >= 4.2
        *)
          printf -v LOG_FILE_NAME_SUFFIX "%(%Y'%m'%d_%H'%M'%S'')T$(( RANDOM % 1000 ))" -1
          ;;
      esac
    
      (
      (
        myfoo1
        #...
        myfooN
    
        # self script reentrance...
        exec $0 "$@"
      ) | tee -a "${SCRIPTS_LOGS_ROOT}/.log/${LOG_FILE_NAME_SUFFIX}.myscript.log" 2>&1
      ) 1>&3 2>&4
    
      exit $?
    }
    
    (( NEST_LVL++ ))
    
    # usual script body goes here...
    

    说明:

    1. 仅重定向输出是不够的,因为您仍然必须将内部echo调用重定向到控制台和文件,因此 pipe-plus-tee 是拆分输出流的唯一方法。
    2. 我们必须使用(...)运算符将​​每个流重定向到一个独立的文件,或者通过单独的步骤将所有流恢复为原始文件。第二个原因是因为myfoo在自我重入之前的调用必须按原样工作而无需任何额外的重定向。
    3. 因为可以从嵌套脚本调用脚本,所以我们必须在顶级调用中仅将文件中的输出重定向一次。所以我们用NEST_LVL变量来表示嵌套调用级别。
    4. 因为重定向可以在外部执行而与脚本无关,所以我们必须通过IMPL_MODE变量显式启用内部重定向。
    5. 我们必须使用日期时间值在每个脚本运行时自动创建一个新的唯一日志文件。
    • 2
  7. Wayne Walker
    2019-12-06T17:29:40+08:002019-12-06T17:29:40+08:00

    TL;DR - 昨天我写了一套记录程序运行和会话的工具。

    目前可在https://github.com/wwalker/quick-log获得

    作为管理员,我总是想记录一些命令的输出,通常不是脚本。为了解决这个问题,我写了一些东西。最简单的方法是使用 xX0v0Xx 提到的程序“脚本”。我发现调用脚本(不带任何参数)通常会导致我覆盖之前脚本的输出。所以我创建了这个别名。它所做的只是防止覆盖。你需要一个 ~/tmp 目录。

    $ alias scr='script ~/tmp/typescript-$(date +%FT%T)'
    $ scr
    Script started, file is /home/wwalker/tmp/typescript-2019-12-05T18:56:31
    $
    

    当我想赶上一个交互式会话时,这很棒。

    当我想记录命令(脚本或二进制文件)的输出时,我要么想要准确的输出,要么想要每行前面带有时间戳的输出。所以我写了这两个bash函数:

    alias iso8601="date +%Y-%m-%dT%H:%M:%S"
    
    justlog(){
      name=$(basename "$1")
      log=~/logs/${name}-$(iso8601)
      "$@" > "$log" 2>&1
    }
    
    timelog(){
      name=$(basename "$1")
      log=~/logs/${name}-$(iso8601)
      # https://github.com/wwalker/ilts
      # You could replace ilts with ts
      # /usr/bin/ts %FT%H:%M:%.S
      "$@" |& ilts -S -E > "$log" 2>&1
    }
    

    像往常一样运行你的命令:

    justlog run rcn rcn-work\\\\\* 'ps -ef -o lstart,cmd | grep [s]upervisor'
    

    或者

    timelog run rcn rcn-work\\\\\* 'ps -ef -o lstart,cmd | grep [s]upervisor'
    

    这刚刚创建了 2 个文件,名为:

    wwalker@polonium:~ ✓ $ ls -l ~/logs/run*
    -rw-r--r-- 1 wwalker wwalker 10495 2019-12-05 18:21:14.985 /home/wwalker/logs/run-2019-12-05T18:21:13
    -rw-r--r-- 1 wwalker wwalker  1694 2019-12-05 18:24:02.878 /home/wwalker/logs/run-2019-12-05T18:24:01
    -rw-r--r-- 1 wwalker wwalker  7623 2019-12-05 18:25:07.873 /home/wwalker/logs/run-2019-12-05T18:25:06
    -rw-r--r-- 1 wwalker wwalker 10296 2019-12-05 18:34:59.546 /home/wwalker/logs/run-2019-12-05T18:34:57
    

    但是,等等还有更多!!

    我不想通过 ls 来查找刚刚为我创建的 justlog 或 timelog 的日志文件的名称。所以,我又增加了 3 个功能:

    newestlog(){
      name=$(basename "$1")
      ls  ~/logs/"${name}"* | tail -1
    }
    
    viewlog(){
      name=$(basename "$1")
      view $( newestlog "$name" )
    }
    
    lesslog(){
      name=$(basename "$1")
      less $( newestlog "$name" )
    }
    

    因此,您使用 justlog(或 timelog)运行您的命令,然后您只需使用 lesslog 或 viewlog(我可能会为这些人创建一个 emacs 日志):

    justlog run rcn rcn-work\\\\\* 'ps -ef -o lstart,cmd | grep [s]upervisor'
    lesslog run
    

    就是这样,不ls ~/tmp,没有标签完成游戏来查找文件名。只需运行 lesslog (或 viewlog,如果你喜欢使用 vim 来查看日志)。

    可是等等!还有更多!

    “我一直在我的日志文件上使用 grep” - 答案是,你猜对了,greplog

    首先,从所有 800 个服务器的 /etc/cron.d/atop 文件中获取已损坏的文本:

    justlog fordcrun 'cat /etc/cron.d/atop; md5dum /etc/cron.d/atop'
    

    然后使用 greplog 获取主机名(在文件中输出上方的行:-)):

    wwalker@polonium:~ ✓ $ greplog fordcrun  -B1 -F "0 0 * * root"
    int-salt-01:
        0 0 * * root systemctl restart atop
    --
    rcn-pg-01:
        0 0 * * root systemctl restart atop
    rcn-pg-02:
        0 0 * * root systemctl restart atop
    rcn-pg-03:
        0 0 * * root systemctl restart atop
    
    • 1
  8. Jose Tavares
    2020-07-29T22:07:05+08:002020-07-29T22:07:05+08:00

    您可以简单地使用“脚本”。

    man script详情。

    例子:

    script -a -e -c "set -x; echo this is logging ..; <your script can be called here>" mylogfile.log
    
    • 0
  9. Matt
    2022-05-28T09:06:04+08:002022-05-28T09:06:04+08:00

    所以,这是我使用的一个选项,并认为它更友好一些。本质上,在脚本的顶部定义一个任务函数,并使用它们自己的函数定义所有部分,以传递给任务函数的调用。它还可以扩展为您的日志添加一些特定的 INFO、WARN、DEBUG 和 ERROR 标志。它们只是使用 echo 以一致的方式包装您的消息的函数,因为所有输出都将进入您的日志。

    它看起来像这样:

    # Append a timestamp and always write a new file
    #LOGFILE=/var/log/my_setup_log_file.$(date +%Y%h%d_%H.%M).log
    # Same log, growing and appending - make sure its a one time deal or that you have a log rotating utility running.
    LOGFILE=/var/log/my_setup_log_file.log
    touch "${LOGFILE}"
    TAIL_PID=$!
    # [Un]comment the following line to control display of this script for the tty in real time.  All other commands should be passed to task.
    tail -f "${LOGFILE}" &
    # logging function - called task to make main section more readable...prepend it to commands, or group commands in a function and prepend the call.
    task() {
        echo "===================================================================================================="
        echo "$(date):$(printf ' %q' "$@")"
        echo "===================================================================================================="
        start=$(date +%s)
        "$@" 2>&1
        end=$(date +%s)
        runtime=$((end-start))
        echo "Elapsed time for command was $runtime seconds."
        echo ""
    } >> "${LOGFILE}"
    
    ### FUNCTION DEFS...
    
    ### Main exectuion
    
    task my_operational_step_1
    task my_operational_step_2
    kill ${TAIL_PID}
    exit 0
    
    

    限制和注意事项:

    1. 我的第一次迭代我选择在附加模式下使用 tee 命令来进行所有日志写入,这需要使用管道。这消除了跨函数使用全局范围变量的能力,因为由于管道,每个函数都有效地在其自己的子shell中运行。啊。
    2. 解决问题 1,我转向在开始时使用 tail -f 作为衍生任务的方法(~第 7 行)。这允许任务功能将所有输出放到日志中,但仍然能够实时查看发生了什么。
    3. 请注意,您需要捕获 tail 的 PID 以便稍后将其杀死,如您在第 8 行附近看到的那样,然后在脚本末尾执行 kill。
    4. 第 3 项的一个推论是,如果你的脚本提前退出,tail 会在后台继续运行......可能值得编写一些陷阱来处理它......考虑到我的范围,我没有为此烦恼我在做。
    5. 我没有广泛使用它,所以你可能会遇到我没有想到的问题。
    6. 使用这种方法可能会出现未记录的内容,因此不是 OP 问题的完美答案。
    • 0

相关问题

  • 多操作系统环境的首选电子邮件客户端

  • 你最喜欢的 Linux 发行版是什么?[关闭]

  • 更改 PHP 的默认配置设置?

  • 保护新的 Ubuntu 服务器 [关闭]

  • (软)Ubuntu 7.10 上的 RAID 6,我应该迁移到 8.10 吗?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    新安装后 postgres 的默认超级用户用户名/密码是什么?

    • 5 个回答
  • Marko Smith

    SFTP 使用什么端口?

    • 6 个回答
  • Marko Smith

    从 IP 地址解析主机名

    • 8 个回答
  • Marko Smith

    如何按大小对 du -h 输出进行排序

    • 30 个回答
  • Marko Smith

    命令行列出 Windows Active Directory 组中的用户?

    • 9 个回答
  • Marko Smith

    什么是 Pem 文件,它与其他 OpenSSL 生成的密钥文件格式有何不同?

    • 3 个回答
  • Marko Smith

    如何确定bash变量是否为空?

    • 15 个回答
  • Martin Hope
    MikeN 在 Nginx 中,如何在维护子域的同时将所有 http 请求重写为 https? 2009-09-22 06:04:43 +0800 CST
  • Martin Hope
    Tom Feiner 如何按大小对 du -h 输出进行排序 2009-02-26 05:42:42 +0800 CST
  • Martin Hope
    0x89 bash中的双方括号和单方括号有什么区别? 2009-08-10 13:11:51 +0800 CST
  • Martin Hope
    Kyle Brandt IPv4 子网如何工作? 2009-08-05 06:05:31 +0800 CST
  • Martin Hope
    Noah Goodrich 什么是 Pem 文件,它与其他 OpenSSL 生成的密钥文件格式有何不同? 2009-05-19 18:24:42 +0800 CST
  • Martin Hope
    Brent 如何确定bash变量是否为空? 2009-05-13 09:54:48 +0800 CST
  • Martin Hope
    cletus 您如何找到在 Windows 中打开文件的进程? 2009-05-01 16:47:16 +0800 CST

热门标签

linux nginx windows networking ubuntu domain-name-system amazon-web-services active-directory apache-2.4 ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve