将最后一个Bash命令从脚本记录到文件 [英] Logging last Bash command to file from script

查看:61
本文介绍了将最后一个Bash命令从脚本记录到文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了许多小脚本来操纵基于Bash的服务器上的文件.我想拥有一种机制,通过该机制可以记录哪些命令在给定目录中创建了哪些文件.但是,我不只是一直想捕获每个输入命令.

I write lots of small scripts to manipulate files on a Bash-based server. I would like to have a mechanism by which to log which commands created which files in a given directory. However, I don't just want to capture every input command, all the time.

方法1 :使用Bash内置命令(la history fc -ln -1 )的包装脚本来捕获最后一个命令并将其写入日志文件.我无法找出任何方法来执行此操作,因为在交互式外壳程序之外似乎无法识别外壳程序内置命令.

Approach 1: a wrapper script that uses a Bash builtin (a la history or fc -ln -1) to grab the last command and write it to a log file. I have not been able to figure out any way to do this, as the shell builtin commands do not appear to be recognized outside of the interactive shell.

方法2 :一个包装器脚本,它从〜/.bash_history 中提取以获取最后一条命令.但是,这需要设置Bash shell来立即将每个命令刷新到历史记录(根据此评论),而且似乎要求允许历史发展势不可挡.如果这是唯一的方法,那就这样,但是最好避免在每个可能实现此代码的系统上都编辑〜/.bashrc 文件.

Approach 2: a wrapper script that pulls from ~/.bash_history to get the last command. This, however, requires setting up the Bash shell to flush every command to history immediately (as per this comment) and seems also to require that the history be allowed to grow inexorably. If this is the only way, so be it, but it would be great to avoid having to edit the ~/.bashrc file on every system where this might be implemented.

方法3 :使用 脚本 .我的问题是,它需要多个命令来启动和停止日志记录,并且因为它启动了自己的外壳程序,所以无法从另一个脚本中调用它(至少这样做会使事情变得非常复杂).

Approach 3: use script. My problem with this is that it requires multiple commands to start and stop the logging, and because it launches its own shell it is not callable from within another script (or at least, doing so complicates things significantly).

我试图找出一种形式为 log_this.script other_script other_arg1 other_arg2>的实现.文件,其中记录了第一个参数之后的所有内容.这里的重点是效率和最小化语法开销.

I am trying to figure out an implementation that's of the form log_this.script other_script other_arg1 other_arg2 > file, where everything after the first argument is logged. The emphasis here is on efficiency and minimizing syntax overhead.

iLoveTux 和我俩都提出了类似的解决方案.对于那些感兴趣的人,下面是我自己的实现.它的功能比接受的答案受到更多的限制,但是它还会通过更改(尽管不是删除)来自动更新任何现有的日志文件条目.

iLoveTux and I both came up with similar solutions. For those interested, my own implementation follows. It is somewhat more constrained in its functionality than the accepted answer, but it also auto-updates any existing logfile entries with changes (though not deletions).

样品用量:

$ cmdlog.py "python3 test_script.py > test_file.txt"

使用以下命令在输出文件的父目录中创建一个日志文件:

creates a log file in the parent directory of the output file with the following:

2015-10-12@10:47:09 test_file.txt   "python3 test_script.py > test_file.txt"

其他文件更改已添加到日志;

Additional file changes are added to the log;

$ cmdlog.py "python3 test_script.py > test_file_2.txt"

该日志现在包含

2015-10-12@10:47:09 test_file.txt   "python3 test_script.py > test_file.txt"
2015-10-12@10:47:44 test_file_2.txt "python3 test_script.py > test_file_2.txt"

再次运行原始文件名会根据文件的修改时间更改日志中的文件顺序:

Running on the original file name again changes the file order in the log, based on modification time of the files:

$ cmdlog.py "python3 test_script.py > test_file.txt"

产生

2015-10-12@10:47:44 test_file_2.txt "python3 test_script.py > test_file_2.txt"
2015-10-12@10:48:01 test_file.txt   "python3 test_script.py > test_file.txt"

完整脚本:

#!/usr/bin/env python3

'''
A wrapper script that will write the command-line
args associated with any files generated to a log
file in the directory where the files were made.

'''
import sys
import os
from os import listdir
from os.path import isfile, join
import subprocess
import time
from datetime import datetime

def listFiles(mypath):
    """
    Return relative paths of all files in mypath

    """
    return [join(mypath, f) for f in listdir(mypath) if
            isfile(join(mypath, f))]

def read_log(log_file):
    """
    Reads a file history log and returns a dictionary
    of {filename: command} entries.

    Expects tab-separated lines of [time, filename, command]

    """
    entries = {}
    with open(log_file) as log:
        for l in log:
            l = l.strip()
            mod, name, cmd = l.split("\t")
            # cmd = cmd.lstrip("\"").rstrip("\"")
            entries[name] = [cmd, mod]
    return entries

def time_sort(t, fmt):
    """
    Turn a strftime-formatted string into a tuple
    of time info

    """
    parsed = datetime.strptime(t, fmt)
    return parsed

ARGS = sys.argv[1]
ARG_LIST = ARGS.split()

# Guess where logfile should be put
if (">" or ">>") in ARG_LIST:
    # Get position after redirect in arg list
    redirect_index = max(ARG_LIST.index(e) for e in ARG_LIST if e in ">>")
    output = ARG_LIST[redirect_index + 1]
    output = os.path.abspath(output)
    out_dir = os.path.dirname(output)
elif ("cp" or "mv") in ARG_LIST:
    output = ARG_LIST[-1]
    out_dir = os.path.dirname(output)
else:
     out_dir = os.getcwd()

# Set logfile location within the inferred output directory
LOGFILE = out_dir + "/cmdlog_history.log"

# Get file list state prior to running
all_files = listFiles(out_dir)
pre_stats = [os.path.getmtime(f) for f in all_files]

# Run the desired external commands
subprocess.call(ARGS, shell=True)

# Get done time of external commands
TIME_FMT = "%Y-%m-%d@%H:%M:%S"
log_time = time.strftime(TIME_FMT)

# Get existing entries from logfile, if present
if LOGFILE in all_files:
    logged = read_log(LOGFILE)
else:
    logged = {}

# Get file list state after run is complete
post_stats = [os.path.getmtime(f) for f in all_files]
post_files = listFiles(out_dir)

# Find files whose states have changed since the external command
changed = [e[0] for e in zip(all_files, pre_stats, post_stats) if e[1] != e[2]]
new = [e for e in post_files if e not in all_files]
all_modded = list(set(changed + new))

if not all_modded:  # exit early, no need to log
    sys.exit(0)

# Replace files that have changed, add those that are new
for f in all_modded:
    name = os.path.basename(f)
    logged[name] = [ARGS, log_time]

# Write changed files to logfile
with open(LOGFILE, 'w') as log:
    for name, info in sorted(logged.items(), key=lambda x: time_sort(x[1][1], TIME_FMT)):
        cmd, mod_time = info
        if not cmd.startswith("\""):
            cmd = "\"{}\"".format(cmd)
        log.write("\t".join([mod_time, name, cmd]) + "\n")

sys.exit(0)

推荐答案

好的,所以您在问题中没有提到Python,但是它被标记为Python,所以我想我可以做什么.我想出了这个脚本:

OK, so you don't mention Python in your question, but it is tagged Python, so I figured I would see what I could do. I came up with this script:

import sys
from os.path import expanduser, join
from subprocess import Popen, PIPE

def issue_command(command):
    process = Popen(command, stdout=PIPE, stderr=PIPE, shell=True)
    return process.communicate()

home = expanduser("~")
log_file = join(home, "command_log")

command = sys.argv[1:]
with open(log_file, "a") as fout:
    fout.write("{}\n".format(" ".join(command)))

out, err = issue_command(command)

您可以这样调用(如果您将其命名为log_this并使其可执行):

which you can call like (if you name it log_this and make it executable):

$ log_this echo hello world

,它将在文件〜/command_log 中放入"echo hello world",但请注意,如果您要使用管道或重定向,则必须引用命令(这可能是一个真正的失败.您的用例,也可能不是,但是我还没有弄清楚没有引号的情况下如何做到这一点:

and it will put "echo hello world" in a file ~/command_log, note though that if you want to use pipes or redirection you have to quote your command (this may be a real downfall for your use case or it may not be, but I haven't figured out how to do this just yet without the quotes) like this:

$ log_this "echo hello world | grep h >> /tmp/hello_world"

但是由于它并不完美,我想我会增加一些额外的内容.

but since it's not perfect, I thought I would add a little something extra.

以下脚本允许您指定其他文件来记录命令并记录命令的执行时间:

The following script allows you to specify a different file to log your commands to as well as record the execution time of the command:

#!/usr/bin/env python
from subprocess import Popen, PIPE
import argparse
from os.path import expanduser, join
from time import time


def issue_command(command):
    process = Popen(command, stdout=PIPE, stderr=PIPE, shell=True)
    return process.communicate()

home = expanduser("~")
default_file = join(home, "command_log")

parser = argparse.ArgumentParser()
parser.add_argument("-f", "--file", type=argparse.FileType("a"), default=default_file)
parser.add_argument("-p", "--profile", action="store_true")
parser.add_argument("command", nargs=argparse.REMAINDER)
args = parser.parse_args()

if args.profile:
    start = time()
    out, err = issue_command(args.command)
    runtime = time() - start
    entry = "{}\t{}\n".format(" ".join(args.command), runtime)
    args.file.write(entry)
else:
    out, err = issue_command(args.command)
    entry = "{}\n".format(" ".join(args.command))
    args.file.write(entry)

args.file.close()

您将使用与其他脚本相同的方式,但是如果您想指定其他文件进行记录,只需在实际命令之前传递 -f< FILENAME> 到那里,如果您想记录执行时间,只需在实际命令之前提供 -p (用于配置文件),即可:

You would use this the same way as the other script, but if you wanted to specify a different file to log to just pass -f <FILENAME> before your actual command and your log will go there, and if you wanted to record the execution time just provide the -p (for profile) before your actual command like so:

$ log_this -p -f ~/new_log "echo hello world | grep h >> /tmp/hello_world"

我会尽力使它变得更好,但是如果您能想到这可以为您做的其他事情,我正在制作一个 github项目,您可以在其中提交错误报告和功能请求.

I will try to make this better, but if you can think of anything else this could do for you, I am making a github project for this where you can submit bug reports and feature requests.

这篇关于将最后一个Bash命令从脚本记录到文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆