我编写了许多小脚本来操作基于Bash的服务器上的文件。我想有一种机制来记录哪些命令在给定目录中创建了哪些文件。但是,我不只是想一直捕获每个输入命令。
方法1 :一个包装器脚本,它使用Bash内置(la history
或fc -ln -1
)来获取最后一个命令并将其写入日志文件。我无法找到任何方法来执行此操作,因为shell内置命令似乎无法在交互式shell之外被识别。
方法2 :一个从~/.bash_history
拉出来获取最后一个命令的包装脚本。但是,这需要设置Bash shell以立即将每个命令刷新到历史记录(根据this comment),并且似乎还要求允许历史记录不可阻挡地增长。如果这是唯一的方法,那就这样吧,但是避免在每个可能实现此问题的系统上编辑~/.bashrc
文件会很棒。
方法3 :使用script
。我的问题是它需要多个命令来启动和停止日志记录,并且因为它启动了自己的shell,所以它不能从另一个脚本中调用(或者至少,这样做会使事情变得非常复杂)。
我试图找出一个log_this.script other_script other_arg1 other_arg2 > file
形式的实现,其中记录了第一个参数之后的所有内容。这里的重点是效率和最小化语法开销。
编辑:iLoveTux我都提出了类似的解决方案。对于那些感兴趣的人,我自己的实现如下它的功能比接受的答案更受限制,但它也会自动更新任何现有的日志文件条目并进行更改(尽管不是删除)。
样本用法:
$ cmdlog.py "python3 test_script.py > test_file.txt"
在输出文件的父目录中创建一个日志文件,其中包含以下内容:
2015-10-12@10:47:09 test_file.txt "python3 test_script.py > test_file.txt"
其他文件更改将添加到日志中;
$ 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"
再次运行原始文件名会根据文件的修改时间更改日志中的文件顺序:
$ 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)
答案 0 :(得分:2)
您可以使用tee
命令将标准输入存储到文件,将其输出到标准输出。将命令行传递到tee
,并将tee
的输出传递给shell的新调用:
echo '<command line to be logged and executed>' | \
tee --append /path/to/your/logfile | \
$SHELL
即,对于other_script other_arg1 other_arg2 > file
的例子,
echo 'other_script other_arg1 other_arg2 > file' | \
tee --append /tmp/mylog.log | \
$SHELL
如果您的命令行需要单引号,则需要正确转义它们。
答案 1 :(得分:1)
好的,所以你不要在你的问题中提到Python,但是它被标记为Python,所以我想我会看到我能做什么。我想出了这个剧本:
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并使其可执行):
$ log_this echo hello world
它会将“echo hello world”放在文件~/command_log
中,但请注意,如果你想使用管道或重定向,你必须引用你的命令(这可能是你的用例真正的垮台或它可能不是,但我还没想出如何在没有引号的情况下这样做:)
$ log_this "echo hello world | grep h >> /tmp/hello_world"
但由于它并不完美,我想我会额外添加一些东西。
以下脚本允许您指定一个不同的文件来记录命令,以及记录命令的执行时间:
#!/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
(用于配置文件),如下所示:
$ log_this -p -f ~/new_log "echo hello world | grep h >> /tmp/hello_world"
我会尽力做到这一点,但如果您能想到其他任何可以为您做的事情,我会为此提出github project,您可以提交错误报告和功能请求。