1

Background: I am trying to automate some parts of a work process. Manual steps I take: I get a ticket to change an ID inside a text file. I have to go through a network shared folder, create a backup of the file, push the copied file into an archive folder, and edit the existing file with the ID from the ticket.

What I am trying to create: A small program that will ask me what ID I am wanting to change(there are pre-existing ID's inside the text file) then it will go inside the file and find the match. Then I want the program to ask me what I want to change the ID to. I want the program to edit the existing file with my input, save it, then close it.

So far I have the following code that completes the first portion (copying the file and pushing it into the archive folder). I have the second function which I am stuck on. I know the second function isn't working, but would like input from others on what they think I should try. I have seen the fileinput module, but I have read that it's not the best module and I should try to program this without the fileinput module.

Code:

import shutil
import datetime
import os
import fileinput

original_file = "\\network\\path\\to\\file"

def date_rename_file():
    todays_date = datetime.datetime.now()
    stripped_time = todays_date.strftime('%Y%m%d')    
    shutil.copyfile(original_file, "\\network\\path\\to\\backupfolder\\device_"+str(stripped_time)+".txt")

def device_id_replace():
    original_id = input("What device ID are you needing to replace?")
    new_id = input("What is the new device ID?")
    with open(original_file, 'w') as devicetxt:
        for line in devicetxt:
            print(line)
            if original_id in line:
                print("Found "+original_id)

date_rename_file()
device_id_replace()

Thanks, feel free to demolish my code :) I'm still learning and would appreciate any input. Also, feel free to let me know if I left any pertinent information out!

  • 1
    Welcome to StackOverflow. Please read and follow the posting guidelines in the help documentation, as suggested when you created this account. [Minimal, complete, verifiable example](http://stackoverflow.com/help/mcve) applies here. We cannot effectively help you until you post your MCVE code and accurately describe the problem. We should be able to paste your posted code into a text file and reproduce the problem you described. StackOverflow is not a coding, review, or tutorial resource. – Prune Dec 18 '18 at 22:09
  • 1
    Most of all, "I am stuck" and "isn't working" are not problem specifications. – Prune Dec 18 '18 at 22:09

3 Answers3

1

You can try this:

def device_id_replace():
    original_id = input("What device ID are you needing to replace?")
    new_id = input("What is the new device ID?")
    with open(original_file, 'r+') as devicetxt: #r+ is for read and write
        string = devicetxt.read() #read into one string
        string = string.replace(original_id, new_id) #replacement
        devicetxt.truncate(0) #To clear contents of file before writeback
        devicetxt.seek(0) #Put the file's current position at 0
        devicetxt.write(string) #writeback to file

Also, it is better practice to pass the original_file as a string to the functions. See editted code below:

import shutil
import datetime
import os
import fileinput

original_file = "\\network\\path\\to\\file"

def date_rename_file(filepath):
    todays_date = datetime.datetime.now()
    stripped_time = todays_date.strftime('%Y%m%d')    
    shutil.copyfile(filepath, "\\network\\path\\to\\backupfolder\\device_"+str(stripped_time)+".txt")

def device_id_replace(filepath):
    original_id = input("What device ID are you needing to replace?")
    new_id = input("What is the new device ID?")
    with open(filepath, 'r+') as devicetxt: #r+ is for read and write
        string = devicetxt.read() #read into one string
        string = string.replace(original_id, new_id) #replacement
        devicetxt.truncate(0) #To clear contents of file before writeback
        devicetxt.seek(0) #Put the file's current position at 0
        devicetxt.write(string) #writeback to file

date_rename_file(original_file)
device_id_replace(original_file)
ycx
  • 3,155
  • 3
  • 14
  • 26
  • Thanks ycx! This seems to work mostly, but I believe it adds all the content back into the same file so I get duplicate data. Going to work on fixing that. Thanks though, exactly what I was looking for. – ZookiePookie Dec 18 '18 at 22:48
  • @ZookiePookie Thanks for spotting that out. I've added an additional line `devicetxt.truncate(0)` to fix exactly that problem. It will clear all contents before the writeback so that you do not get duplicate data. Let me know if it helps. Functionally, it is also better to open the file once in a `for` loop and do read/write than to open it twice in 2 `for` loops separately to do read, then write. – ycx Dec 19 '18 at 02:49
  • so I tried the new truncate in my code. It doesn't create duplicates which is good, but it adds a lot of white space into the top of the text file. I will try to debug what step is causing that. Thank you! – ZookiePookie Dec 19 '18 at 15:58
  • if you have time could you explain the comment you made here: "Also, it is better practice to pass the original_file as a string to the functions. See editted code below:" I am trying to understand why we pass parameters into the functions. Also, where does the 'filepath' parameter come from? Any reading you could send link me to to understand that better? – ZookiePookie Dec 19 '18 at 16:38
  • @ZookiePookie To solve the white space problem, I've added a `.seek(0)` to set the cursor at the file's 0 position. Sorry I missed that out during testing. What happens is that `.truncate(0)` deletes everything from 0 position, but the cursor is still at the last place it was at. So `.seek(0)` solves that. – ycx Dec 19 '18 at 22:32
  • @ZookiePookie It is better practice because you can use the function in different scenarios since you can now put the function into a `for` loop that calls it multiple times with different filepaths if need be. It is also not hardcoded to read only from the filepath specified in the global domain of the script but in the local domain of the function. Furthermore, if this script were to be imported, it will still work. `filepath` parameter is just a name, you can put `file`, `donkey`, etc there and it will work, it is simply a guide to programmers who will be calling that function to know what – ycx Dec 19 '18 at 22:36
  • to use for passing in arguments into the function. In this case, I passed `original_file` inside there. – ycx Dec 19 '18 at 22:37
  • Thanks YCX for the answer helps clarify that concept in my head a lot better. Playing around with your code it looks like the original id and new id do not get replaced. Not sure why that is, I thought I had it working yesterday, but doing a search in the file after shows that nothing gets substituted – ZookiePookie Dec 20 '18 at 21:31
  • @ZookiePookie Edited further and simplified the code now that I better understand your requirements. There is no need to `readlines()` and `writelines()` since you do not need them in a `list`. See the edited new answer for shorter and faster code. – ycx Dec 21 '18 at 06:57
  • Thanks ycx! Looks like this method is working for me, I am going to add some if else logic, but I believe this is exactly what I was looking for thank you for coming back and editing it so often and helping me understand some python concepts! – ZookiePookie Dec 21 '18 at 15:33
1

Try this one:

original_file = ""\\network\\path\\to\\file""

def device_id_replace(filepath):                   
    original_id = input("What device ID are you needing to replace?") 
    new_id = input("What is the new device ID?")   
    with open(filepath, 'r') as devicetxt:
        new_content = []
        for line in devicetxt:
            new_content.append(line.replace(original_id, new_id))
    with open(filepath, "w") as devicetxt:
        for line in new_content:
            devicetxt.write(line)

device_id_replace(original_file)

The answer of @ycx was not working in the past. So I fixed your old code:

original_file = "test.txt"


def device_id_replace(filepath):
    original_id = input("What device ID are you needing to replace?")
    new_id = input("What is the new device ID?")
    with open(filepath, 'r+') as devicetxt: #r+ is for read and write
        contents = devicetxt.readlines() #put all lines into a list
        for line_i, line in enumerate(contents):
            if original_id in line:
                contents[line_i] = line.replace(original_id, new_id) # CHANGED LINE
        devicetxt.truncate(0)
        devicetxt.seek(0)
        devicetxt.writelines(contents) #writeback to file

device_id_replace(original_file)
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
guru
  • 357
  • 2
  • 13
  • Tried this as well! Saved me from troubleshooting ycx's suggestion. Thanks very much for your help! This is working as I want it to! I see there's an empty list that we append each line into, are we essentially rewriting each line into the same file? – ZookiePookie Dec 18 '18 at 22:59
  • The code block with open(..., 'r')... reads the file and the code block with open(..., "w")... overwrites the old file. It is also possible to open the file once and rewrite it with the "r+" parameter. – guru Dec 18 '18 at 23:06
  • @ZookiePookie would you be interested in code, which does open the file once and does the replacement directly? If yes, I would like it to solve this problem too. – guru Dec 18 '18 at 23:16
  • Found such a solution, but do not think it would be better. But keep in mind, that in the case of a crash you lose your data. It would be better to write a completely new file and rename it to the old one afterwards. – guru Dec 19 '18 at 00:02
  • 1
    thanks for looking into that for me. Your method works and @ycx method works as well. I'd like you to look at ycx's method as it only has one for loop in it and makes the code more readable and succinct which from what I've learned so far is part of the 'python zen'. – ZookiePookie Dec 19 '18 at 14:44
  • Similar approach I have used (seek and truncate). I would say you gain not much, but lose readability. I personally prefer separated read and write block in this case. But the substitution does not work. You cannot change the element of an for element in list in this manner (https://stackoverflow.com/questions/19290762/cant-modify-list-elements-in-a-loop-python) – guru Dec 20 '18 at 14:33
  • 1
    I think you're right about ycx's method. I am not seeing any substitution occurring when I run it. I am trying to add an if statement below 'for line in devicetxt:'. I want the if else to say 'if original_id in line:' 'print("Found " + original_id)' then perform 'new_content.append(line.replace(original_id, new_id))'. 'else:' 'print("Please ensure the PC ID was typed in correctly"). However, when I do this it always goes straight to the 'else' portion of the code. Any idea on why it's not working as expected? – ZookiePookie Dec 20 '18 at 22:10
  • I reposted the example from @ycx with one line changed to fix the bug. – guru Dec 21 '18 at 16:53
  • Oh he has changed it already x). You can see the for element in list as a read only access, to avoid it use the enumerate trick. Would be happy for a upvote and I think also @ycx would be happy to get one ;) – guru Dec 21 '18 at 17:04
  • Added an upvote! I hope you got it, I am pretty new to stack overflow so I hope that means pressing the up arrow next to your post. Thanks for your help, you and @ycx are awesome! – ZookiePookie Dec 21 '18 at 17:29
  • Oh wait, someone upvoted my answer and someone mean downvoted my answer, so maybe you upvoted my answer. Result: I helped without getting reputation x). Please one pity upvote for my comment x) x) x) – guru Dec 21 '18 at 17:42
  • Thx to the anonymous person. – guru Dec 21 '18 at 18:38
0

You have a few options here, either use an internal tool like sed if you are using Unix/Unix like, or create a new file on top of the old one.

  1. Read all the file.
  2. Replace occurrences.
  3. Rewrite all lines.

This a pythonic version

with open(filepath) as f:
    transformedLines = [line.replace(old, new) if (old in line) else (line) for line in f]
with open(filepath, 'w') as f:
    f.writelines(transformedLines);

A less Pythonic version is similar:

transformedLines = []
with open(filepath) as f:
    for line in f:
        if old in line:
            transformedLines.append(line.replace(old, new))
        else:
            transformedLines.append(line)

A functional way (albeit very inefficient) can be done with map:

    transformedLines = list(map(lambda line : line.replace(old, new), f.readlines()))

This should be alright if your file isn’t huge, otherwise you will have to think about using a different type of data store like a DB.

Your approach works well at the moment but you will want to open it in read mode, for each line append it to a new list, if your id is found append the new line instead.

Write the new array to the file opened in write mode, which will wipe out the contents.

OHHH
  • 1,011
  • 3
  • 16
  • 34
  • Great suggestion - I will try this. I tried @ycx method and it seems to get close to what I am wanting, but I believe it is updating the ID and then it is writing everything it read into the file again so I am getting duplicate data. – ZookiePookie Dec 18 '18 at 22:46
  • I added some details – OHHH Dec 19 '18 at 01:49