我有一个 bash 脚本,它试图通过运行命令并将script -c
输出写入日志文件来一石二鸟,以便我可以监控进度,然后通过电子邮件给自己发送结果。
最终的日志文件相当长,因为它是终端会话中显示的所有内容的打字稿;每个进度输出都会被记录。但是,如果我使用 读取数据cat
,我只能得到终端中显示的最终输出。
例如:
script -c 'rsync -ah --info=progress2 folder1 folder2' logfile.log
使用以下命令打开文件nano
:
> # nano logfile.log
Script started on 2021-07-20 14:22:40+0800
^M 36.84M 0% 34.31GB/s 0:00:00 (xfr#1, to-chk=606/673)^M 808.26M 7% 752.75GB/s 0:00:00 (xfr#31, to-chk=603/673)^M 860.63M 7% 801.52GB/s 0:00:00 (xfr#34, to-chk=592/673)$
Script done on 2021-07-20 14:22:40+0800
鉴于,与cat
> # cat logfile.log
Script started on 2021-07-20 14:22:40+0800
11.48G 100% 10693.06GB/s 0:00:00 (xfr#616, to-chk=0/673)
Script done on 2021-07-20 14:22:40+0800
但是,如果将cat
输出写入文件:
> # cat logfile.log > temp.log
结果temp.log
将包括整个原始数据。
差异的原因是什么?
我想通过电子邮件发送与我从
cat
显示器上获得的相同的输出;不是显示的原始输出nano
。但是,cat
始终将原始数据输出到文件、另一个命令等。
下面的命令通过电子邮件发送原始数据。
> # echo -e "Subject : report" "\n\n\n `cat logfile.log`" | sendmail [email protected]
- 有什么办法可以清除打字稿文件中的所有原始数据吗?我在网上或手册中没有找到任何内容。
补充评论
感谢您的建议和解释。事实上,我可以按照评论中的建议清理with和
的输出。rsync
tr
sed
但是,如果我在没有程序的情况下运行命令-v
或运行其他程序(例如)restic
,日志将在日志中显示大量擦除序列。
^[[2K[13:34] 487210 files 165.864 GiB, total 503917 files 230.290 GiB, 0 errors
^[[2K[13:34] 487218 files 165.972 GiB, total 503960 files 230.477 GiB, 0 errors
^[[2KFiles: 176 new, 3 changed, 633544 unmodified
^[[2K
^[[2KDirs: 260 new, 140 changed, 106144 unmodified
^[[2K
^[[2KAdded to the repo: 363.231 MiB
^[[2K
^[[2K
^[[2K
^[[2Kprocessed 633723 files, 535.105 GiB in 14:34
^[[2K
^[[2Ksnapshot 9394ca81 saved
^[[2K
^[[2K
^[[1A
Script done on 2021-07-20 00:06:12+0800
我可以设法删除那些带有sed
.
如果有一种方法可以将终端的最后输出直接写入cat
文件或通过管道传输到文件,那么它可能是更干净、更通用的解决方案sponge
?
答案1
好的,这是一个(相对)简单的 Python 脚本,它尝试进行此清理。我称之为clean-typescript.py
。它当然可以改进,并且可能包含错误,但这是我可以在短时间内想出的。
#!/usr/bin/env python3
# Takes raw terminal output of a program containing control sequences that
# overwrite parts of the output, and attempts to transform it to just the end
# result, writing its output on stdout.
#
# Assumptions/limitations:
# * Assumes Unix line endings.
# * Assumes input text is left-to-right, in system character encoding.
# * Does not attempt to deal with most of the complexities of Unicode.
# * Does not attempt to interpret every ANSI escape sequence; just the common
# ones that affect cursor position.
# * Ignores ANSI SGR (bold/color/etc.) sequences.
# * Assumes 8-column tab stops.
# * Assumes the terminal displays an unlimited number of lines.
# * Ignores absolute positioning sequences (except CHA): this is not for
# ncurses output and such.
# * Will not allow the cursor to go up beyond the first line in the file.
#
# Usage: clean-typescript.py FILE COLS
# FILE is the name of the file to read; if omitted or "-", read stdin.
# COLS is how many columns wide the terminal is; if omitted, set to 80.
import sys
from array import array
from enum import Enum
if len(sys.argv) >= 2 and sys.argv[1] != "-":
f = open(sys.argv[1], "r")
else:
f = sys.stdin
if len(sys.argv) >= 3:
cols = int(sys.argv[2])
else:
cols = 80
lines = [array("u", (" ",)*cols)]
curline = curcol = 0
eol = False
class Dir (Enum):
UP = 0
DOWN = 1
RIGHT = 2
LEFT = 3
def move_cursor (dir: Dir, count: int):
global curline, curcol, eol
if dir == Dir.UP:
pos = curline - count
curline = pos if pos >= 0 else 0
elif dir == Dir.DOWN:
pos = curline + count
curline = pos if pos < len(lines) else len(lines) - 1
elif dir == Dir.RIGHT:
pos = curcol + count
curcol = pos if pos < cols else cols - 1
elif dir == Dir.LEFT:
eol = False
pos = curcol - count
curcol = pos if pos >= 0 else 0
def skip_osc ():
c = f.read(1)
while True:
if c == "\x07":
return f.read(1)
if c == "\x1b":
if f.read(1) == "\\":
return f.read(1)
c = f.read(1)
def interpret_seq ():
c = f.read(1)
if c == "]": # OSC
return skip_osc()
if c != "[": # CSI
# Other Fe seqs. not supported
return f.read(1)
parms = []
c = f.read(1)
while True:
p = ""
while c >= "0" and c <= "9":
p += c
c = f.read(1)
if p:
parms.append(int(p))
if c != ";": break
c = f.read(1)
if c == "A": # CUU
move_cursor(Dir.UP, parms[0] if len(parms) > 0 else 1)
elif c == "B": # CUD
move_cursor(Dir.DOWN, parms[0] if len(parms) > 0 else 1)
elif c == "C": # CUF
move_cursor(Dir.RIGHT, parms[0] if len(parms) > 0 else 1)
elif c == "D": # CUB
move_cursor(Dir.LEFT, parms[0] if len(parms) > 0 else 1)
elif c == "E": # CNL
move_cursor(Dir.LEFT, cols)
move_cursor(Dir.DOWN, parms[0] if len(parms) > 0 else 1)
elif c == "F": # CPL
move_cursor(Dir.LEFT, cols)
move_cursor(Dir.UP, parms[0] if len(parms) > 0 else 1)
elif c == "G": # CHA
move_cursor(Dir.LEFT, cols)
move_cursor(Dir.RIGHT, parms[0] - 1 if len(parms) > 0 else 0)
# CUP and ED not implemented
elif c == "K": # EL
if (len(parms) == 0 or parms[0] == 0) and not eol:
for i in range(curcol, cols):
lines[curline][i] = " "
elif parms[0] == 1:
for i in range(0, curcol):
lines[curline][i] = " "
if eol:
append_line()
move_cursor(Dir.LEFT, cols)
move_cursor(Dir.DOWN, 1)
elif parms[0] == 2:
for i in range(0, cols):
lines[curline][i] = " "
if eol:
append_line()
move_cursor(Dir.LEFT, cols)
move_cursor(Dir.DOWN, 1)
# ED, SU, SD, and HVP also not implemented
c = f.read(1)
return c
def append_line ():
lines.append(array("u", (" ",)*cols))
c = f.read(1)
while c:
if c == "\x08": # BS
if eol:
eol = False
else:
move_cursor(Dir.LEFT, 1)
elif c == "\x09": # HT
curcol = (curcol + 8)//8*8
if curcol >= cols: curcol = cols - 1
elif c == "\x0a": # LF (implies CR in Unix)
eol = False
if curline == len(lines) - 1: append_line()
move_cursor(Dir.LEFT, cols)
move_cursor(Dir.DOWN, 1)
elif c == "\x0b" or c == "\x0c": # VT/FF: just go down one line
if curline == len(lines) - 1: append_line()
move_cursor(Dir.DOWN, 1)
elif c == "\x0d": # CR (stays on same line)
eol = False
move_cursor(Dir.LEFT, cols)
elif c == "\x1b": # Escape seq.
c = interpret_seq()
continue
elif (c >= "\x20" and c <= "\x7e") or c >= "\xa1":
if not eol: lines[curline][curcol] = c
if curcol == cols - 1:
if eol:
if curline == len(lines) - 1: append_line()
move_cursor(Dir.LEFT, cols)
move_cursor(Dir.DOWN, 1)
lines[curline][curcol] = c
move_cursor(Dir.RIGHT, 1)
else:
eol = True
else:
move_cursor(Dir.RIGHT, 1)
c = f.read(1)
# Final result
print(*("".join(line).rstrip() for line in lines), sep="\n", end="")