簡體   English   中英

將最后一個Bash命令從腳本記錄到文件

[英]Logging last Bash command to file from script

我編寫了許多小腳本來操縱基於Bash的服務器上的文件。 我想擁有一種機制,通過該機制可以記錄哪些命令在給定目錄中創建了哪些文件。 但是,我不僅想一直捕獲所有輸入命令。

方法1 :使用Bash內置命令(la historyfc -ln -1 )的包裝器腳本抓取最后一個命令並將其寫入日志文件。 由於無法在交互式外殼程序之外識別外殼程序內置命令,因此我無法找出任何方法來執行此操作。

方法2 :從~/.bash_history中獲取最后一條命令的包裝器腳本。 但是,這需要設置Bash shell來立即將每個命令刷新到歷史記錄(根據此注釋 ),並且似乎還要求允許歷史記錄無情地增長。 如果這是唯一的方法,那就這樣,但是最好避免在可能實現此功能的每個系統上都編輯~/.bashrc文件。

方法三 :使用script 我的問題是,它需要多個命令來啟動和停止日志記錄,並且由於它啟動了自己的外殼程序,因此無法從另一個腳本中調用它(至少這樣做會使事情變得非常復雜)。

我試圖找出一種形式為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)

您可以使用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

如果您的命令行需要單引號,則需要對其進行正確的轉義。

好的,所以您在問題中沒有提到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項目 ,您可以在其中提交錯誤報告和功能請求。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM