edit text file using Python

2019-01-17 13:41发布

I need to update a text file whenever my IP address changes, and then run a few commands from the shell afterwards.

  1. Create variable LASTKNOWN = "212.171.135.53" This is the ip address we have while writing this script.

  2. Get the current IP address. It will change on a daily basis.

  3. Create variable CURRENT for the new IP.

  4. Compare (as strings) CURRENT to LASTKNOWN

  5. If they are the same, exit()

  6. If they differ,

    A. "Copy" the old config file (/etc/ipf.conf) containing LASTKNOWN IP address into /tmp B. Replace LASTKNOWN with CURRENT in the /tmp/ipf.conf file.
    C. Using subprocess "mv /tmp/ipf.conf /etc/ipf.conf"
    D. Using subprocess execute, "ipf -Fa -f /etc/ipf.conf"
    E. Using subprocess execute, "ipnat -CF -f /etc/ipnat.conf"

  7. exit()

I know how to do steps 1 through 6. I fall down on the "file editing" part, A -> C. I can't tell what module to use or whether I should be editing the file in place. There are so many ways to do this, I can't decide on the best approach. I guess I want the most conservative one.

I know how to use subprocess, so you don't need to comment on that.

I don't want to replace entire lines; just a specific dotted quad.

Thanks!

6条回答
家丑人穷心不美
2楼-- · 2019-01-17 13:44

Another way to simply edit files in place is to use the fileinput module:

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)
查看更多
混吃等死
3楼-- · 2019-01-17 13:46

Probably the simplest way would be to open a file using f=open(filename, mode). Then, read all the lines using f.readlines() (this will return a list of strings representing the lines of the program).

You can then search these strings to find the address and replace it with the new one (using standard string replacing, regular expressions, or whatever you want).

At the end, you can write the lines back to the file using f.writelines(lines), which conveniently takes back a list of lines.

NOTE: This is not an efficient way to do this, it's just the easiest. Please

Example code:

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.
查看更多
姐就是有狂的资本
4楼-- · 2019-01-17 13:54

You're trying to "atomically" update the contents of a file, and there have been many delightful flame wars on the subject. But the general pattern is:

1) Write the new file to a temp file, and make sure you flush and close.

2) Use your operating system's facilities to atomically rename the temp file to the old file.

Now, you simply can't atomically rename a file in Windows, but it sounds like you're on a unix-like system anyway. You atomically rename using os.rename().

查看更多
Fickle 薄情
5楼-- · 2019-01-17 14:04

Replace LASTKNOWN by CURRENT in /etc/ipf.conf

Replace all at once

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

Replace line by line

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)

Note: "/tmp/ipf.conf" should be replaced by tempfile.NamedTemporaryFile() or similar
Note: the code is not tested.

查看更多
聊天终结者
6楼-- · 2019-01-17 14:05

After checking the comments and the bit of code that you put on pastebin, here is a working solution. To begin with, the file /tmp/iiiipf.conf contains:

Simply a test file 175.48.204.168

And two times 175.48.204.168 on this line 175.48.204.168

Done.

After running the code, the file /tmp/iiiipf.conf contains:

Simply a test file 10.73.144.112

And two times 10.73.144.112 on this line 10.73.144.112

Done.

And here is the tested working code with my stuff merged into your pastebin code:

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()

This bit of code will do what you need even if the IP address is used several times within the configuration file. It will also change it in comment lines.

This method does not require copying to /tmp and uses one less subprocess call when restarting the firewall and NAT.

查看更多
放荡不羁爱自由
7楼-- · 2019-01-17 14:11

fileinput module has very ugly API, I find beautiful module for this task - in_place, example for 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)

main difference from fileinput:

  • Instead of hijacking sys.stdout, a new filehandle is returned for writing.
  • The filehandle supports all of the standard I/O methods, not just readline().

for example - fileinput can line by line editing only, in_pace allow read whole file to memory (if it not big) and modify it.

查看更多
登录 后发表回答