【问题标题】:edit text file using Python使用 Python 编辑文本文件
【发布时间】:2023-04-04 05:14:01
【问题描述】:

每当我的 IP 地址发生变化时,我都需要更新一个文本文件,然后从 shell 运行一些命令。

  1. 创建变量 LASTKNOWN = "212.171.135.53" 这是我们编写此脚本时的 IP 地址。

  2. 获取当前 IP 地址。每天都会变化。

  3. 为新 IP 创建变量 CURRENT。

  4. 比较(作为字符串)CURRENT 和 LASTKNOWN

  5. 如果相同,则退出()

  6. 如果它们不同,

    A.将包含 LASTKNOWN IP 地址的旧配置文件(/etc/ipf.conf)“复制”到 /tmp B. 将 /tmp/ipf.conf 文件中的 LASTKNOWN 替换为 CURRENT。
    C. 使用子进程“mv /tmp/ipf.conf /etc/ipf.conf”
    D. 使用子进程执行,“ipf -Fa -f /etc/ipf.conf”
    E. 使用子进程执行,“ipnat -CF -f /etc/ipnat.conf”

  7. 退出()

我知道如何执行第 1 步到第 6 步。我陷入了“文件编辑”部分,A -> C。我不知道要使用哪个模块或是否应该在适当的位置编辑文件。有很多方法可以做到这一点,我无法决定最好的方法。我想我想要最保守的。

我知道如何使用子流程,因此您无需对此发表评论。

我不想替换整行;只是一个特定的虚线四边形。

谢谢!

【问题讨论】:

  • “有很多方法可以做到这一点,我无法决定最好的方法。”由于您正在寻找一种最佳方式,而不仅仅是任何方式,也许最好多说一点约束:答案是否必须非常有效(内存或运行时间)等?
  • 不,它不需要超级高效。我正在编辑的文件很小。但也许将来我需要对一个 20MB 的 15,000 行的文本文件执行这种类型的操作。我猜你可以告诉我一个更有效的方法。
  • mv 命令(及其 python 等价物shutil.move())不能保证是原子的。最好将新版本放在同一个目录下(最简单的放在同一个逻辑驱动器上的方法),然后使用os.rename()调用进行原子重命名。

标签: python text editing


【解决方案1】:

另一种简单地编辑文件的方法是使用fileinput 模块:

import fileinput, sys
for line in fileinput.input(["test.txt"], inplace=True):
    line = line.replace("car", "truck")
    # sys.stdout is redirected to the file
    sys.stdout.write(line)

【讨论】:

  • "text.txt" 周围的方括号 [] 不是必需的。
【解决方案2】:

将 /etc/ipf.conf 中的 LASTKNOWN 替换为 CURRENT

一次性全部更换

filename = "/etc/ipf.conf"
text = open(filename).read()
open(filename, "w").write(text.replace(LASTKNOWN, CURRENT))

逐行替换

from __future__ import with_statement
from contextlib import nested

in_filename, outfilename = "/etc/ipf.conf", "/tmp/ipf.conf"
with nested(open(in_filename), open(outfilename, "w")) as in_, out:
     for line in in_:
         out.write(line.replace(LASTKNOWN, CURRENT))
os.rename(outfilename, in_filename)

注意:“/tmp/ipf.conf”应替换为tempfile.NamedTemporaryFile()或类似
注意:代码未经测试。

【讨论】:

  • 是的。谢谢。 ./pyifchk-0.py:23:警告:'with' 将成为 Python 2.6 文件“./pyifchk-0.py”中的保留关键字,第 23 行带有嵌套(open(in_filename),open(outfilename,“w ")) as in_, out: ^ SyntaxError: invalid syntax
  • ^ 指向 "nested" 和 "(in_filename)" 的连接处。我用嵌套的 (in_filename), open(outfilename, "w")) as in_, out: 尝试了它们之间的空格并得到了同样的错误。
  • @javadong:在 Python 2.5 上,您需要 from __future__ import with_statement
【解决方案3】:

fileinput 模块的 API 非常丑陋,我为这项任务找到了漂亮的模块 - in_place,Python 3 的示例:

import in_place

with in_place.InPlace('data.txt') as file:
    for line in file:
        line = line.replace('test', 'testZ')
        file.write(line)

与文件输入的主要区别:

  • 不是劫持 sys.stdout,而是返回一个新的文件句柄以供写入。
  • 文件句柄支持所有标准 I/O 方法,而不仅仅是 readline()。

例如 - fileinput 只能逐行编辑,in_pace 允许将整个文件读取到内存(如果它不大)并修改它。

【讨论】:

  • in_place 是否只从当前目录读取文件?我尝试使用读取服务器中的文件(使用 Telenetlib),但我收到一个错误,即当前(python)目录中不存在特定文件。
  • 不是,它可以从路径读取文件,例如使用 in_place.InPlace('/home/worldmind/back.svg') 作为文件:
【解决方案4】:

您正在尝试“原子地”更新文件的内容,并且在该主题上发生了许多令人愉快的激烈争吵。但一般模式是:

1) 将新文件写入临时文件,并确保刷新并关闭。

2) 使用操作系统的工具将临时文件自动重命名为旧文件。

现在,您根本无法在 Windows 中以原子方式重命名文件,但听起来您无论如何都在一个类似 unix 的系统上。你使用os.rename()自动重命名。

【讨论】:

  • +1:“就地更新”是一个疯狂的尝试。新建和重命名更简单。
  • 是的,这个脚本将在 BSD 上运行。是的,我一直无法弄清楚如何使用 Python “就地编辑文件”。这可能是可能的,但似乎并不容易。
  • 那么...我的回答能满足您的需要吗?
  • 不。 “将新文件写入临时文件”使用什么?新文件中是否已经包含新的 IP 地址?是不是更像是“将现有文件复制到 /tmp 并在其中执行字符串替换,然后将其移动到 /etc”?
  • 至少涉及三个步骤 ;)
【解决方案5】:

可能最简单的方法是使用 f=open(filename, mode) 打开文件。然后,使用 f.readlines() 读取所有行(这将返回代表程序行的字符串列表)。

然后您可以搜索这些字符串以找到地址并将其替换为新地址(使用标准字符串替换、正则表达式或任何您想要的)。

最后,您可以使用 f.writelines(lines) 将行写回文件,它可以方便地取回行列表。

注意:这不是一种有效的方法,它只是最简单的方法。请

示例代码:

f = open(filename, "r")
lines = f.readlines()

# Assume that change_ip is a function that takes a string and returns a new one with the ip changed): example below
ret_lines = [change_ip(lines) for line in lines]
new_file = open(new_filename, "w")
new_file.writelines(lines)

def change_ip(str):
   ''' Gets a string, returns a new string where the ip is changed '''
   # Add implementation, something like: return str.replace(old_ip, new_ip) or something similair.

【讨论】:

  • 谢谢。我会试试这个。这是我开始的路径之一。
  • 如果文件是由其他进程读取和监视的文件,这种方法可能会导致其他进程看到不一致的状态,可能包含无意义的内容。如果这是一个问题,请写入临时文件,同步该文件,然后将其移动到正确的位置。
  • 不确定在这种情况下您所说的“同步”是什么意思。从什么同步到什么?
  • 另外,“change_ip”看起来像一个“动词”,但我想你打算让我把新 IP 地址的字符串放在那里。请记住,我已经将“已知”和“当前”IP 地址存储为 str() 类型的变量
  • LASTKNOWN = '175.48.234.168' import socket import fileinput import subprocess import string import re CURRENT = socket.getaddrinfo(socket.gethostname(), None)[0][4][0] # the如果 CURRENT == LASTKNOWN: print 'Nevermind.',上面得到我的实际当前 IP。 subprocess.sys.exit() 其他:
【解决方案6】:

在检查了 cmets 和您放在 pastebin 上的代码后,这是一个可行的解决方案。首先,文件 /tmp/iiiipf.conf 包含:

Simply a test file 175.48.204.168

And two times 175.48.204.168 on this line 175.48.204.168

Done.

运行代码后,文件/tmp/iiiipf.conf包含:

Simply a test file 10.73.144.112

And two times 10.73.144.112 on this line 10.73.144.112

Done.

这是经过测试的工作代码,我的东西合并到你的 pastebin 代码中:

import socket
import fileinput
import subprocess
import string
import re

CURRENT = socket.getaddrinfo(socket.gethostname(), None)[0][4][0]
LASTKNOWN = '175.48.204.168'

if CURRENT == LASTKNOWN:
    print 'Nevermind.'
    subprocess.sys.exit()

else:

    cf = open("/tmp/iiiipf.conf", "r")
    lns = cf.readlines()
    # close it so that we can open for writing later
    cf.close()

    # assumes LASTKNOWN and CURRENT are strings with dotted notation IP addresses
    lns = "".join(lns)
    lns = re.sub(LASTKNOWN, CURRENT, lns)  # This replaces all occurences of LASTKNOWN with CURRENT

    cf = open("/tmp/iiiipf.conf", "w")
    cf.write(lns)
    cf.close()

即使在配置文件中多次使用 IP 地址,这段代码也能满足您的需求。它还会在注释行中更改它。

此方法不需要复制到 /tmp,并且在重启防火墙和 NAT 时使用更少的子进程调用。

【讨论】:

  • 谢谢,这看起来可行。但是 ipf 和 ipnat 仍然需要“重新启动”。 Ipfilter 无法自行查看对其配置文件的更改。它需要刷新旧规则并安装新规则。 “刷新” ipnat 也很有意义。我从 1999 年开始使用 ipf/ipnat,这是唯一可靠的方法。
  • 当我尝试使用上述内容时,我得到了:root@spork# ./pyifchk.py Traceback(最近一次调用最后):文件“./pyifchk.py”,第 22 行,在 lns = re.sub(LASTKNOWN,CURRENT,lns) 文件“/usr/local/lib/python2.5/re.py”,第 150 行,在 sub return _compile(pattern, 0).sub(repl, string,计数)类型错误:预期的字符串或缓冲区
  • 这就是我说“代码片段”的原因。您需要将其插入到执行其他操作的脚本中,包括将字符串值分配给 CURRENT 和 LASTKNOWN。或者,如果您对保存 IP 地址的字符串有不同的变量名称,则在我的示例中更改名称。我依靠你知道何时何地调用子进程来停止和启动防火墙和 NAT,因为我对 Solaris 的细节不太熟悉。
  • 再一次,不要寻求 BSD 或 Solaris 或 ipfilter 的帮助。 lns = cf.readlines() 返回一个 LIST,而不是一个字符串。
  • 不,应该是:lns = (re.sub(LASTKNOWN, CURRENT, l) for l in lns)。调用readline() 只是获取文件的第一行;该生成器表达式对每一行进行替换并将结果作为可迭代对象返回,write 将对其进行迭代。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-12
  • 1970-01-01
  • 1970-01-01
  • 2021-09-28
  • 2015-01-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多