I am writing a Python 2 program to find a file. This program should print each directory it searches at each iteration of the search, but always to the same line in the terminal (i.e. by erasing the text that is already there and moving the cursor to the beginning of the line before printing again.)
This is the code I have so far:
import os
import sys
for root, dirs, files in os.walk("/"):
print root +'\r',
print '\x1b[2K\r',
My problem is that it starts each printout (when it change directory) on a new line; in other words, it doesn't reuse the old line.
How can I ensure all printed output goes to a single line in the terminal?
You need to flush the stdout buffer (depends on the terminal system), and pad the line with whitespace. For example:
for root, dirs, files in os.walk(path):
print "%-80s\r" % (root),
sys.stdout.flush()
time.sleep(1) # For testing
This assumes an arbitrary maximum filename length of 80 characters.
EDIT:
This new solution uses curses, which is part of the standard library:
import curses
import os
import time
win = curses.initscr()
for root, dirs, files in os.walk(path):
win.clear()
win.addstr(0, 0, root)
win.refresh()
time.sleep(1) # For testing purposes
curses.endwin()
This should do it.
for root, dirs, files in os.walk(path):
print '\r', root,
The \r tells python to rewind to the beginning of the current line, like old typewriters.
You might want to pad with spaces to erase the rest of the line, if the current path is shorter than the previous path.
If the text is longer than one line, it will still overflow to the next line.
You need to shorten your output to under the terminal limit.
You could just truncate and put ellipsis at the front:
limit = 30 # for example
message = 'ABCDEFGHIJKLMNOPQRSTUVWX' * 4
if len(message) > limit:
message = '...' + message[-limit+3:]
print message # ...VWXABCDEFGHIJKLMNOPQRSTUVWX
If you want to replace the middle with ..., then you could do:
limit = 30 # for example
message = 'ABCDEFGHIJKLMNOPQRSTUVWX' * 4
length = len(message) # will be 100
if length > limit:
message = list(message)
cut_size = length - limit
start_cut = (length - cut_size) / 2
message[start_cut:start_cut + cut_size + 3] = '...'
message = ''.join(message)
print message # ABCDEFGHIJKLMNO...MNOPQRSTUVWX
Inspired by several ideas from here and there, this works for me well:
import os
import sys
import time # only if you use sleep() function for debugging
top_folder = "/"
max_line_length = 80
for root, dirs, files in os.walk(top_folder):
message = root
# truncate if the path longer than what you want it to be
if len(message) > max_line_length:
message = '[...]' + message[-max_line_length+5:]
# prepare the output string of lenght determined by a variable
output_string = '{0: <' + str(max_line_length) + '}\r' # \r = carret return
# output
print output_string.format(message), # the comma is crucial here
# to see it in action in slow-motion
time.sleep(.4)
The last 2 code lines before the sleep() function line could be combined into one line:
print '{msg: <{width}}\r'.format(msge = message, width = max_line_length),
Related
I am trying to find and replace several lines of plain text in multiple files with input() but when I enter '\n' characters to represent where the new line chars would be in the text, it doesn't find it and doesn't replace it.
I tried to use raw_strings but couldn't get them to work.
Is this a job for regular expressions?
python 3.7
import os
import re
import time
start = time.time()
# enter path and check input for standard format
scan_folder = input('Enter the absolute path to scan:\n')
validate_path_regex = re.compile(r'[a-z,A-Z]:\\?(\\?\w*\\?)*')
mo = validate_path_regex.search(scan_folder)
if mo is None:
print('Path is not valid. Please re-enter path.\n')
import sys
sys.exit()
os.chdir(scan_folder)
# get find/replaceStrings, and then confirm that inputs are correct.
find_string = input('Enter the text you wish to find:\n')
replace_string = input('Enter the text to replace:\n')
permission = input('\nPlease confirm you want to replace '
+ find_string + ' with '
+ replace_string + ' in ' + scan_folder
+ ' directory.\n\nType "yes" to continue.\n')
if permission == 'yes':
change_count = 0
# Context manager for results file
with open('find_and_replace.txt', 'w') as results:
for root, subdirs, files in os.walk(scan_folder):
for file in files:
# ignore files that don't endwith '.mpr'
if os.path.join(root, file).endswith('.mpr'):
fullpath = os.path.join(root, file)
# context manager for each file opened
with open(fullpath, 'r+') as f:
text = f.read()
# only add to changeCount if find_string is in text
if find_string in text:
change_count += 1
# move cursor back to beginning of the file
f.seek(0)
f.write(text.replace(find_string, replace_string))
results.write(str(change_count)
+ ' files have been modified to replace '
+ find_string + ' with ' + replace_string + '.\n')
print('Done with replacement')
else:
print('Find and replace has not been executed')
end = time.time()
print('Program took ' + str(round((end - start), 4)) + ' secs to complete.\n')
find_string = BM="LS"\nTI="12"\nDU="7"
replace_string = BM="LSL"\nDU="7"
The original file looks like
BM="LS"
TI="12"
DU="7"
and I would like it to change to
BM="LSL"
DU="7"
but the file doesn't change.
So, the misconception you have is the distinction between source code, which understands escape sequences like "this is a string \n with two lines", and things like "raw strings" (a concept that doesn't make sense in this context) and the data your are providing as user input. The input function basically processes data coming in from the standard input device. When you provide data to standard input, it is being interpreted as a raw bytes and then the input function assumes its meant to be text (decoded using whatever your system setting imply). There are two approaches to allow a user to input newlines, the first is to use sys.stdin, however, this will require you to provide an EOF, probably using ctrl + D:
>>> import sys
>>> x = sys.stdin.read()
here is some text and i'm pressing return
to make a new line. now to stop input, press control d>>> x
"here is some text and i'm pressing return\nto make a new line. now to stop input, press control d"
>>> print(x)
here is some text and i'm pressing return
to make a new line. now to stop input, press control d
This is not very user-friendly. You have to either pass a newline and an EOF, i.e. return + ctrl + D or do ctrl + D twice, and this depends on the system, I believe.
A better approach would be to allow the user to input escape sequences, and then decode them yourself:
>>> x = input()
I want this to\nbe on two lines
>>> x
'I want this to\\nbe on two lines'
>>> print(x)
I want this to\nbe on two lines
>>> x.encode('utf8').decode('unicode_escape')
'I want this to\nbe on two lines'
>>> print(x.encode('utf8').decode('unicode_escape'))
I want this to
be on two lines
>>>
I'm new to Python and working on a little program that copies all files of given extension from a folder and it's subfolders to an another directory. Recently I added a simple progress bar and a counter of remaining files.
The problem is that when I run it from cmd and counter comes from say 1000 to 999 cmd adds a zero in the place of a last digit instead of space. Moreover, when the program is finished remaining files counter should be substituted by the word "Done." and it also doesn't work well.
I tried to replace sys.stdout.write with print and tried not to use f-strings, the result is the same.
def show_progress_bar(total, counter=0, length=80):
percent = round(100 * (counter / total))
filled_length = int(length * counter // total)
bar = '=' * filled_length + '-' * (length - filled_length)
if counter < total:
suffix = f'Files left: {total - counter}'
else:
suffix = 'Done.'
sys.stdout.write(f'\rProgress: |{bar}| {percent}% {suffix}')
sys.stdout.flush()
def selective_copy(source, destination, extension):
global counter
show_progress_bar(total)
for foldername, subfolders, filenames in os.walk(source):
for filename in filenames:
if filename.endswith(extension):
if not os.path.exists(os.path.join(destination, filename)):
shutil.copy(os.path.join(foldername, filename), os.path.join(destination, filename))
else:
new_filename = f'{os.path.basename(foldername)}_{filename}'
shutil.copy(os.path.join(foldername, filename), os.path.join(destination, new_filename))
counter += 1
show_progress_bar(total, counter)
I expected that the output in cmd will be the same as in the console, which is this:
Program running:
Progress: |=========-----------------------------------------------------------------------| 12% Files left: 976
Program finished:
Progress: |================================================================================| 100% Done.
But in the cmd I got this:
Program running:
Progress: |=========-----------------------------------------------------------------------| 12% Files left: 9760
Program finished:
Progress: |================================================================================| 100% Done. left: 100
Typically, printing "\r" will return the cursor to the beginning of the line, but it won't erase anything already written. So if you write "1000" followed by "\r" followed by "999", the last 0 of "1000" will still be visible.
(I'm not sure why this isn't happening in your Python console. Maybe it interprets "\r" in a different way. Hard to say without knowing exactly what software you're running.)
One solution is to print a couple of spaces after your output to ensure that slightly longer old messages get overwritten. You can probably get away with just one space for your "Files left:" suffix, since that only decreases by one character at most, but the "done" suffix will need more.
if counter < total:
suffix = f'Files left: {total - counter} '
else:
suffix = 'Done. '
I want to print Text 'Loading...' But its dots would be moving back and forward (in shell).
I am creating a text game and for that it will look better.
I know writing slowly a word but dots also have to go back.
I am thinking that I should forget dots to come back.And for that:
import sys
import time
shell = sys.stdout.shell
shell.write('Loading',"stdout")
str = '........'
for letter in str:
sys.stdout.write(letter)
time.sleep(0.1)
What do you think?
If you have that dots would be moving back and forward Then please share with me.
If you want more information I am ready to Provide to you.
Thanks
You can use backtracking via backspace (\b) in your STDOUT to go back and 'erase' written characters before writing them again to simulate animated loading, e.g.:
import sys
import time
loading = True # a simple var to keep the loading status
loading_speed = 4 # number of characters to print out per second
loading_string = "." * 6 # characters to print out one by one (6 dots in this example)
while loading:
# track both the current character and its index for easier backtracking later
for index, char in enumerate(loading_string):
# you can check your loading status here
# if the loading is done set `loading` to false and break
sys.stdout.write(char) # write the next char to STDOUT
sys.stdout.flush() # flush the output
time.sleep(1.0 / loading_speed) # wait to match our speed
index += 1 # lists are zero indexed, we need to increase by one for the accurate count
# backtrack the written characters, overwrite them with space, backtrack again:
sys.stdout.write("\b" * index + " " * index + "\b" * index)
sys.stdout.flush() # flush the output
Keep in mind that this is a blocking process so you either have to do your loading checks within the for loop, or run your loading in a separate thread, or run this in a separate thread - it will keep running in a blocking mode as long as its local loading variable is set to True.
Check This Module Keyboard with many features. Install It, perhaps with this command:
pip3 install keyboard
Then Write the following code in File textdot.py:
def text(text_to_print,num_of_dots,num_of_loops):
from time import sleep
import keyboard
import sys
shell = sys.stdout.shell
shell.write(text_to_print,'stdout')
dotes = int(num_of_dots) * '.'
for last in range(0,num_of_loops):
for dot in dotes:
keyboard.write('.')
sleep(0.1)
for dot in dotes:
keyboard.write('\x08')
sleep(0.1)
Now Paste the file in Lib from your python folder.
Now you Can use it like following example:
import textdot
textdot.text('Loading',6,3)
Thanks
A bit late but for anyone else its not that complicated.
import os, time #import os and time
def loading(): #make a function called loading
spaces = 0 #making a variable to store the amount of spaces between the start and the "."
while True: #infinite loop
print("\b "*spaces+".", end="", flush=True) #we are deleting however many spaces and making them " " then printing "."
spaces = spaces+1 #adding a space after each print
time.sleep(0.2) #waiting 0.2 secconds before proceeding
if (spaces>5): #if there are more than 5 spaces after adding one so meaning 5 spaces (if that makes sense)
print("\b \b"*spaces, end="") #delete the line
spaces = 0 #set the spaces back to 0
loading() #call the function
I believe the following code is what you are looking for. Simply thread this in your script, and it will flash dots while the user is waiting.
################################################################################
"""
Use this to show progress in the terminal while other processes are runnning
- show_running.py -
"""
################################################################################
#import _thread as thread
import time, sys
def waiting(lenstr=20, zzz=0.5, dispstr='PROCESSING'):
dots = '.' * lenstr
spaces = ' ' * lenstr
print(dispstr.center(lenstr, '*'))
while True:
for i in range(lenstr):
time.sleep(zzz)
outstr = dots[:i] + spaces[i:]
sys.stdout.write('\b' * lenstr + outstr)
sys.stdout.flush()
for i in range(lenstr, 0, -1):
time.sleep(zzz)
outstr = dots[:i] + spaces[i:]
sys.stdout.write('\b' * lenstr + outstr)
sys.stdout.flush()
#------------------------------------------------------------------------------#
if __name__ == '__main__':
import _thread as thread
from tkinter import *
root = Tk()
Label(root, text="I'm Waiting").pack()
start = time.perf_counter()
thread.start_new_thread(waiting, (20, 0.5))
root.mainloop()
finish = time.perf_counter()
print('\nYour process took %.2f seconds to complete.' % (finish - start))
I have gone through following scenarios
which we can list files in a directory
Which we can list files with their file sizes
But now I need to list only the files just they are greater than some X KB with the input given by the user.
Please help with some suitable examples
Here is my code
import os
for path, dirs, files in os.walk("PathToDir" ):
for f in files:
size=os.path.getsize( os.path.join( path, f )
print path, f, size
Here's an example of how to go 'walk' through the files in a directory, and then printing out the ones that meet a file size criterion:
Note: How to 'walk' was found here:
concatenate the directory and file name
# Task: List only files that are greater than some X KB with the input given by the user.
import os
# The directory that we are interested in
myPath = "/users/george/documents/"
# The min size of the file in Bytes
mySize = '10000'
# All the file paths will be stored in this list
filesList= []
for path, subdirs, files in os.walk(myPath):
for name in files:
filesList.append(os.path.join(path, name))
for i in filesList:
# Getting the size in a variable
fileSize = os.path.getsize(str(i))
# Print the files that meet the condition
if int(fileSize) >= int(mySize):
print "The File: " + str(i) + " is: " + str(fileSize) + " Bytes"
I achieved it using the pathlib module. I am running Python 3.7.6 on Windows.
Here's the code:
import os
from pathlib import Path
dir_path = Path('//?/D:/TEST_DIRECTORY')
# IMP_NOTE: If the path is 265 characters long, which exceeds the classic MAX_PATH - 1 (259) character
# limit for DOS paths. Use an extended (verbatim) path such as "\\\\?\\C:\\" in order
# to access the full length that's supported by the filesystem -- about 32,760 characters.
# Alternatively, use Windows 10 with Python 3.6+ and enable long DOS paths in the registry.
# pathlib normalizes Windows paths to use backslash, so we can use
# Path('//?/D:/') without having to worry about escaping backslashes.
F_LIST = list(x for x in dir_path.rglob('*.*') if x.is_file() and os.path.getsize(x) >= 10000)
for f in F_LIST:
print(f.parts[-1] + " ===> " + "Size = " + str(format(os.path.getsize(f), ',d')) + "\n")
# path.parts ==> Provides a tuple giving access to the path’s various components
# (Ref.: pathlib documentation)
Hope this helps! :-)
limit = raw_input('Enter a file size: ')
if int(limit) > 0:
import os
for path, dirs, files in os.walk("PathToDir" ):
for f in files:
size=os.path.getsize( os.path.join( path, f )
if size > limit :
print path, f, size
I have a large list of images that have been misnamed by my artist. I was hoping to avoid giving him more work by using Automator but I'm new to it. Right now they're named in order what001a and what002a but that should be what001a and what001b. So basically odd numbered are A and even numbered at B. So i need a script that changes the even numbered to B images and renumbers them all to the proper sequential numbering. How would I go about writing that script?
A small Ruby script embedded in an AppleScript provides a very comfortable solution, allowing you to select the files to rename right in Finder and displaying an informative success or error message.
The algorithm renames files as follows:
number = first 3 digits in filename # e.g. "006"
letter = the letter following those digits # e.g. "a"
if number is even, change letter to its successor # e.g. "b"
number = (number + 1)/2 # 5 or 6 => 3
replace number and letter in filename
And here it is:
-- ask for files
set filesToRename to choose file with prompt "Select the files to rename" with multiple selections allowed
-- prepare ruby command
set ruby_script to "ruby -e \"s=ARGV[0]; m=s.match(/(\\d{3})(\\w)/); n=m[1].to_i; a=m[2]; a.succ! if n.even?; r=sprintf('%03d',(n+1)/2)+a; puts s.sub(/\\d{3}\\w/,r);\" "
tell application "Finder"
-- process files, record errors
set counter to 0
set errors to {}
repeat with f in filesToRename
try
do shell script ruby_script & (f's name as text)
set f's name to result
set counter to counter + 1
on error
copy (f's name as text) to the end of errors
end try
end repeat
-- display report
set msg to (counter as text) & " files renamed successfully!\n"
if errors is not {} then
set AppleScript's text item delimiters to "\n"
set msg to msg & "The following files could NOT be renamed:\n" & (errors as text)
set AppleScript's text item delimiters to ""
end if
display dialog msg
end tell
Note that it will fail when the filename contains spaces.
A friend of mine wrote a Python script to do what I needed. Figured I'd post it here as an answer for anyone stumbling upon a similar problem looking for help. It is in Python though so if anyone wants to convert it to AppleScript for those that may need it go for it.
import os
import re
import shutil
def toInt(str):
try:
return int(str)
except:
return 0
filePath = "./"
extension = "png"
dirList = os.listdir(filePath)
regx = re.compile("[0-9]+a")
for filename in dirList:
ext = filename[-len(extension):]
if(ext != extension): continue
rslts = regx.search(filename)
if(rslts == None): continue
pieces = regx.split(filename)
if(len(pieces) < 2): pieces.append("")
filenumber = toInt(rslts.group(0).rstrip("a"))
newFileNum = (filenumber + 1) / 2
fileChar = "b"
if(filenumber % 2): fileChar = "a"
newFileName = "%s%03d%s%s" % (pieces[0], newFileNum, fileChar, pieces[1])
shutil.move("%s%s" % (filePath, filename), "%s%s" % (filePath, newFileName))