by Al Sweigart
Step 2: Save Clipboard Content with a Keyword
The program does different things depending on whether the user wants to save text to a keyword, load text into the clipboard, or list all the existing keywords. Let’s deal with that first case. Make your code look like the following:
#! python3 # mcb.pyw - Saves and loads pieces of text to the clipboard. --snip-- # Save clipboard content. ➊ if len(sys.argv) == 3 and sys.argv[1].lower() == 'save': ➋ mcbShelf[sys.argv[2]] = pyperclip.paste() elif len(sys.argv) == 2: ➌ # TODO: List keywords and load content. mcbShelf.close()
If the first command line argument (which will always be at index 1 of the sys.argv list) is 'save' ➊, the second command line argument is the keyword for the current content of the clipboard. The keyword will be used as the key for mcbShelf, and the value will be the text currently on the clipboard ➋.
If there is only one command line argument, you will assume it is either 'list' or a keyword to load content onto the clipboard. You will implement that code later. For now, just put a TODO comment there ➌.
Step 3: List Keywords and Load a Keyword’s Content
Finally, let’s implement the two remaining cases: The user wants to load clipboard text in from a keyword, or they want a list of all available keywords. Make your code look like the following:
#! python3 # mcb.pyw - Saves and loads pieces of text to the clipboard. --snip-- # Save clipboard content. if len(sys.argv) == 3 and sys.argv[1].lower() == 'save': mcbShelf[sys.argv[2]] = pyperclip.paste() elif len(sys.argv) == 2: # List keywords and load content. ➊ if sys.argv[1].lower() == 'list': ➋ pyperclip.copy(str(list(mcbShelf.keys()))) elif sys.argv[1] in mcbShelf: ➌ pyperclip.copy(mcbShelf[sys.argv[1]]) mcbShelf.close()
If there is only one command line argument, first let’s check whether it’s 'list' ➊. If so, a string representation of the list of shelf keys will be copied to the clipboard ➋. The user can paste this list into an open text editor to read it.
Otherwise, you can assume the command line argument is a keyword. If this keyword exists in the mcbShelf shelf as a key, you can load the value onto the clipboard ➌.
And that’s it! Launching this program has different steps depending on what operating system your computer uses. See Appendix B for details for your operating system.
Recall the password locker program you created in Chapter 6 that stored the passwords in a dictionary. Updating the passwords required changing the source code of the program. This isn’t ideal because average users don’t feel comfortable changing source code to update their software. Also, every time you modify the source code to a program, you run the risk of accidentally introducing new bugs. By storing the data for a program in a different place than the code, you can make your programs easier for others to use and more resistant to bugs.
Summary
Files are organized into folders (also called directories), and a path describes the location of a file. Every program running on your computer has a current working directory, which allows you to specify file paths relative to the current location instead of always typing the full (or absolute) path. The os.path module has many functions for manipulating file paths.
Your programs can also directly interact with the contents of text files. The open() function can open these files to read in their contents as one large string (with the read() method) or as a list of strings (with the readlines() method). The open() function can open files in write or append mode to create new text files or add to existing text files, respectively.
In previous chapters, you used the clipboard as a way of getting large amounts of text into a program, rather than typing it all in. Now you can have your programs read files directly from the hard drive, which is a big improvement, since files are much less volatile than the clipboard.
In the next chapter, you will learn how to handle the files themselves, by copying them, deleting them, renaming them, moving them, and more.
Practice Questions
Q:
1. What is a relative path relative to?
Q:
2. What does an absolute path start with?
Q:
3. What do the os.getcwd() and os.chdir() functions do?
Q:
4. What are the . and .. folders?
Q:
5. In C:baconeggsspam.txt, which part is the dir name, and which part is the base name?
Q:
6. What are the three “mode” arguments that can be passed to the open() function?
Q:
7. What happens if an existing file is opened in write mode?
Q:
8. What is the difference between the read() and readlines() methods?
Q:
9. What data structure does a shelf value resemble?
Practice Projects
For practice, design and write the following programs.
Extending the Multiclipboard
Extend the multiclipboard program in this chapter so that it has a delete
Mad Libs
Create a Mad Libs program that reads in text files and lets the user add their own text anywhere the word ADJECTIVE, NOUN, ADVERB, or VERB appears in the text file. For example, a text file may look like this:
The ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN was unaffected by these events.
The program would find these occurrences and prompt the user to replace them.
Enter an adjective: silly Enter a noun: chandelier Enter a verb: screamed Enter a noun: pickup truck
The following text file would then be created:
The silly panda walked to the chandelier and then screamed. A nearby pickup truck was unaffected by these events.
The results should be printed to the screen and saved to a new text file.
Regex Search
Write a program that opens all .txt files in a folder and searches for any line that matches a user-supplied regular expression. The results should be printed to the screen.
Chapter 9. Organizing Files
In the previous chapter, you learned how to create and write to new files in Python. Your programs can also organize preexisting files on the hard drive. Maybe you’ve had the experience of going through a folder full of dozens, hundreds, or even thousands of files and copying, renaming, moving, or compressing them all by hand. Or consider tasks such as these:
Making copies of all PDF files (and only the PDF files) in every sub-folder of a folder
Removing the leading zeros in the filenames for every file in a folder of hundreds of files named spam001.txt, spam002.txt, spam003.txt, and so on
Compressing the contents of several folders into one ZIP file (which could be a simple backup system)
All this boring stuff is just begging to be automated in Python. By programming your computer to do these tasks, you can transform it into a quick-working file clerk who never makes mistakes.
As you begin working with files, you may find it helpful to be able to quickly see what the extension (.txt, .pdf, .jpg, and so on) of a file is. With OS X and Linux, your file browser most likely shows extensions automatically. With Windows, file extensions may be hidden by default. To show extensions, go to Start▸ Control Panel▸Appearance and Personalization▸Folder Options. On the View tab, under Advanced Settings, uncheck the Hide extensions for known file types checkbox.
The shutil Module
The shutil (or shell utilities) module has functions to let you copy, move, rename, and delete files in your Python programs. To use the shutil functions, you will first need to use import shutil.
Copying Files and Folders
The shutil module provides functions for copying files, as well as entire folders.
Calling shutil.copy(source, destination) will copy the file at the path source to the folder at the path destination. (Both source and destination are strings.) If
destination is a filename, it will be used as the new name of the copied file. This function returns a string of the path of the copied file.
Enter the following into the interactive shell to see how shutil.copy() works:
>>> import shutil, os >>> os.chdir('C:\') ➊ >>> shutil.copy('C:\spam.txt', 'C:\delicious') 'C:\delicious\spam.txt' ➋ >>> shutil.copy('eggs.txt', 'C:\delicious\eggs2.txt') 'C:\delicious\eggs2.txt'
The first shutil.copy() call copies the file at C:spam.txt to the folder C:delicious. The return value is the path of the newly copied file. Note that since a folder was specified as the destination ➊, the original spam.txt filename is used for the new, copied file’s filename. The second shutil.copy() call ➋ also copies the file at C:eggs.txt to the folder C:delicious but gives the copied file the name eggs2.txt.
While shutil.copy() will copy a single file, shutil.copytree() will copy an entire folder and every folder and file contained in it. Calling shutil.copytree(source, destination) will copy the folder at the path source, along with all of its files and subfolders, to the folder at the path destination. The source and destination parameters are both strings. The function returns a string of the path of the copied folder.
Enter the following into the interactive shell:
>>> import shutil, os >>> os.chdir('C:\') >>> shutil.copytree('C:\bacon', 'C:\bacon_backup') 'C:\bacon_backup'
The shutil.copytree() call creates a new folder named bacon_backup with the same content as the original bacon folder. You have now safely backed up your precious, precious bacon.
Moving and Renaming Files and Folders
Calling shutil.move(source, destination) will move the file or folder at the path source to the path destination and will return a string of the absolute path of the new location.
If destination points to a folder, the source file gets moved into destination and keeps its current filename. For example, enter the following into the interactive shell:
>>> import shutil >>> shutil.move('C:\bacon.txt', 'C:\eggs') 'C:\eggs\bacon.txt'
Assuming a folder named eggs already exists in the C: directory, this shutil.move() calls says, “Move C:bacon.txt into the folder C:eggs.”
If there had been a bacon.txt file already in C:eggs, it would have been overwritten. Since it’s easy to accidentally overwrite files in this way, you should take some care when using move().
The destination path can also specify a filename. In the following example, the source file is moved and renamed.
>>> shutil.move('C:\bacon.txt', 'C:\eggs\new_bacon.txt') 'C:\eggs\new_bacon.txt'
This line says, “Move C:bacon.txt into the folder C:eggs, and while you’re at it, rename that bacon.txt file to new_bacon.txt.”
Both of the previous examples worked under the assumption that there was a folder eggs in the C: directory. But if there is no eggs folder, then move() will rename bacon.txt to a file named eggs.
>>> shutil.move('C:\bacon.txt', 'C:\eggs') 'C:\eggs'
Here, move() can’t find a folder named eggs in the C: directory and so assumes that destination must be specifying a filename, not a folder. So the bacon.txt text file is renamed to eggs (a text file without the .txt file extension)—probably not what you wanted! This can be a tough-to-spot bug in your programs since the move() call can happily do something that might be quite different from what you were expecting. This is yet another reason to be careful when using move().
Finally, the folders that make up the destination must already exist, or else Python will throw an exception. Enter the following into the interactive shell:
>>> shutil.move('spam.txt', 'c:\does_not_exist\eggs\ham') Traceback (most recent call last): File "C:Python34libshutil.py", line 521, in move os.rename(src, real_dst) FileNotFoundError: [WinError 3] The system cannot find the path specified: 'spam.txt' -> 'c:\does_not_exist\eggs\ham' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "
Python looks for eggs and ham inside the directory does_not_exist. It doesn’t find the nonexistent directory, so it can’t move spam.txt to the path you specified.
Permanently Deleting Files and Folders
You can delete a single file or a single empty folder with functions in the os module, whereas to delete a folder and all of its contents, you use the shutil module.
Calling os.unlink(path) will delete the file at path.
Calling os.rmdir(path) will delete the folder at path. This folder must be empty of any files or folders.
Calling shutil.rmtree(path) will remove the folder at path, and all files and folders it contains will also be deleted.
Be careful when using these functions in your programs! It’s often a good idea to first run your program with these calls commented out and with print() calls added to show the files that would be deleted. Here is a Python program that was intended to delete files that have the .txt file extension but has a typo (highlighted in bold) that causes it to delete .rxt files instead:
import os for filename in os.listdir(): if filename.endswith('.rxt'): os.unlink(filename)
If you had any important files ending with .rxt, they would have been accidentally, permanently deleted. Instead, you should have first run the program like this:
import os for filename in os.listdir(): if filename.endswith('.rxt'): #os.unlink(filename) print(filename)
Now the os.unlink() call is commented, so Python ignores it. Instead, you will print the filename of the file that would have been deleted. Running this version of the program first will show you that you’ve accidentally told the program to delete .rxt files instead of .txt files.
Once you are certain the program works as intended, delete the print(filename) line and uncomment the os.unlink(filename) line. Then run the program again to actually delete the files.
Safe Deletes with the send2trash Module
Since Python’s built-in shutil.rmtree() function irreversibly deletes files and folders, it can be dangerous to use. A much better way to delete files and folders is with the third-party send2trash module. You can install this module by running pip install send2trash from a Terminal window. (See Appendix A for a more in-depth explanation of how to install third-party modules.)
Using send2trash is much safer than Python’s regular delete functions, because it will send folders and files to your computer’s trash or recycle bin instead of permanently deleting them. If a bug in your program deletes something with send2trash you didn’t intend to delete, you can later restore it from the recycle bin.
After you have installed send2trash, enter the following into the interactive shell:
>>> import send2trash >>> baconFile = open('bacon.txt', 'a') # creates the file >>> baconFile.write('Bacon is not a vegetable.') 25 >>> baconFile.close() >>> send2trash.send2trash('bacon.txt')
In general, you should always use the send2trash.send2trash() function to delete files and folders. But while sending files to the recycle bin lets you recover them later, it will not free up disk space like permanently deleting them does. If you want your program to free up disk space, use the os and shutil functions for deleting files and folders. Note that the send2trash() function can only send files to the recycle bin; it cannot pull files out of it.
Walking a Directory Tree
Say you want to rename every file in some folder and also every file in every subfolder of that folder. That is, you want to walk through the directory tree, touching each file as you go. Writing a program to do this could get tricky; fortunately, Python provides a function to handle this process for you.
Let’s look at the C:delicious folder with its contents, shown in Figure 9-1.
Figure 9-1. An example folder that contains three folders and four files
Here is an example program that uses the os.walk() function on the directory tree from Figure 9-1:
import os for folderName, subfolders, filenames in os.walk('C:\delicious'): print('The current folder is ' + folderName) for subfolder in subfolders: print('SUBFOLDER OF ' + folderName + ': ' + subfolder) for filename in filenames: print('FILE INSIDE ' + folderName + ': '+ filename) print('')
The os.walk() function is passed a single string value: the path of a folder. You can use os.walk() in a for loop statement to walk a directory tree, much like how you can use the range() function to walk over a range of numbers. Unlike range(), the os.walk() function will return three values on each iteration through the loop: