How to escape a spacebar in a path name with subprocess? - python

I'm trying to convert a file from .m4a to .mp3 using ffmpeg and I need to access to the music folder.
The path name of this folder is : C:\\Users\A B\Desktop\Music
I can't access it with subprocess.call() because only C:\\Users\A gets recognized. The white space is not processed.
Here's my python script :
import constants
import os
import subprocess
path = 'C:\\Users\A B\Desktop\Music'
def main():
files = sorted(os.listdir(path), key=lambda x: os.path.getctime(os.path.join(path, x)))
if "Thumbs.db" in files: files.remove("Thumbs.db")
for f in files:
if f.lower()[-3:] == "m4a":
process(f)
def process(f):
inFile = f
outFile = f[:-3] + "mp3"
subprocess.call('ffmpeg -i {} {} {}'.format('C:\\Users\A B\Desktop\Music', inFile, outFile))
main()
When I run it I get an error that states :
C:\Users\A: No such file or directory
I wonder if someones knows how to put my full path name (C:\Users\A B\Desktop\Music) in subprocess.call() ?

Beforehand edit: spaces or not, the following command line -i <directory> <infilename> <outfilename> is not correct for ffmpeg since it expects the -i option, then input file and output file, not a directory first. So you have more than one problem here (which explains the "permission denied" message you had, because ffmpeg was trying to open a directory as a file!)
I suppose that you want to:
read all files from directory
convert them all to a file located in the same directory
In that case, you could add quotes to your both input & output absolute files like this:
subprocess.call('ffmpeg -i "{0}\{1}" "{0}\{2}"'.format('C:\\Users\A B\Desktop\Music', inFile, outFile))
That would work, but that's not the best thing to do: not very performant, using format when you already have all the arguments already, you may not have knowledge of other characters to escape, etc... don't reinvent the wheel.
The best way to do it is to pass the arguments in a list so subprocess module handles the quoting/escaping when necessary:
path = r'C:\Users\A B\Desktop\Music' # use raw prefix to avoid backslash escaping
subprocess.call(['ffmpeg','-i',os.path.join(path,inFile), os.path.join(path,outFile)])
Aside: if you're the user in question, it's even better to do:
path = os.getenv("USERPROFILE"),'Desktop','Music'
and you could even run the process in the path directory with cwd option:
subprocess.call(['ffmpeg','-i',inFile, outFile],cwd=path)
and if you're not, be sure to run the script with elevated privileges or you won't get access to another user directory (read-protected)

Related

Add to ~/.zshrc file with python

I'm trying to write a cli that will take a users path that they input into the command line, then add this path to the correct path file depending on their shell - in this case zsh. I have tried using:
shell = str(subprocess.check_output("echo $SHELL", shell=True))
click.echo("Enter the path you would like to add:")
path = input()
if 'zsh' in shell:
with open(".zshrc", 'w') as zsh:
zsh.write(f'export PATH="$PATH:{path}"')
This throws no errors but doesn't seem to add to the actual ~./zshrc file.
Is there a better way to append to the file without manually opening the file and typing it in?
New to this so sorry if it's a stupid question...
Open the file in append mode. Your code also assumes that the current working directory is the user's home directory, which is not a good assumption to make.
from pathlib import Path
import os
if 'zsh' in os.environ.get("SHELL", ""):
with open(Path.home() / ".zshrc", 'a') as f:
f.write(f'export PATH={path}:$PATH')
with (Path.home() / ".zshrc').open("a") as f: would work as well.
Note that .zprofile would be the preferred location for updating an envirornent variable like PATH, rather than .zshrc.
Solved! Just want to put the answer here if anyone comes across the same problem.
Instead of trying to open the file with
with open(".zshrc", 'w') as zsh:
zsh.write(f'export PATH="$PATH:{path}"')
you can just do
subprocess.call(f"echo 'export PATH=$PATH:{path}' >> ~/.zshrc", shell=True)
If anybody has a way of removing from ~/.zshrc with python that would be very helpful...

subprocess.call() to remove files

I want to remove all the files and directories except for some of them by using
`subprocess.call(['rm','-r','!(new_models|creat_model.py|my_mos.tit)'])`
but it gives back information
rm: cannot remove `!(new_models|creat_model.py|my_mos.tit)': No such file or directory
how can I fix this? Thanks
If you use that rm command on the command line the !(…|…|…) pattern is expanded by the shell into all file names except those in the pattern before calling rm. Your code calls rm directly so rm gets the shell pattern as a file name and tries to delete a file with that name.
You have to add shell=True to the argument list of subprocess.call() or actually code this in Python instead of calling external commands. Downside: That would be more than one line. Upside: it can be done independently from external shells and system dependent external programs.
An alternative to shell=True could be the usage of glob and manual filtering:
import glob
files = [i for i in glob.glob("*") if i not in ('new_models', 'creat_model.py', 'my_mos.tit')]
subprocess.call(['rm','-r'] + files)
Edit 4 years later:
Without glob (of which I don't remember why I suggested it):
import os
files = [i for i in os.listdir() if i not in ('new_models', 'creat_model.py', 'my_mos.tit')]
subprocess.call(['rm','-r'] + files)
Code to remove all png
args = ('rm', '-rf', '/path/to/files/temp/*.png')
subprocess.call('%s %s %s' % args, shell=True)

Printing all files in shell

The program txt.py prints that takes in a directory as a command line parameter and prints all the files of a particular extension
def printAll(path):
for txtFile in glob.glob(path):
print txtFile
printAll(sys.argv[1])
In the command line I type in python txt.py /home/Documents/*.txt
It prints only the first txt file.
How do I print all txt files in that directory given we pass the directory from command line?
At the command line the * is a shell wildcard, your shell expands it and passes a list of files to the script, e.g.
python txt.py "/home/Documents/1.txt" "/home/Documents/2.txt" "/home/Documents/3.txt"
You only look at the first argument, so only print one file.
You need to escape the * in your shell so it gets through to Python as a single argument with an asterisk. In bash shell, maybe:
python txt.py "/home/Documents/\*.txt"
Edit: As an alternative you could just take the directory path on the command line, and add the *.txt part in your program, e.g.
def printAll(path):
path = path + "/*.txt"
for txtFile in glob.glob(path):
print txtFile
printAll(sys.argv[1])
call it with:
$ python txt.py "/home/Documents"
Edit 2: How about passing an example file in the path and the script can find files with the same file extension as that? It doesn't even have to exist. That sounds fun:
import os, sys
def printAll(path, fileext):
query = os.path.join(path, '*.' + fileext)
for someFile in glob.glob(query):
print someFile
printAll(sys.argv[1], sys.argv[2])
call it with
$ python txt.py /home/Documents txt
(os.path.join is a utility that adds / if it's needed)
Or how about passing an example file in the path and the script can find files with the same file extension as that? It doesn't even have to exist. That sounds fun:
import os, sys
def printAll(path):
searchdir, filename = os.path.split(path)
tmp, fileext = os.path.splitext(filename)
path = os.path.join(searchdir, '*'.fileext)
for someFile in glob.glob(path):
print someFile
printAll(sys.argv[1])
call it with:
$ python txt.py "/home/Documents/example.txt"
Thanks to how the shell works, you are actually passing in the entire list of files, but only using the first one. This is because the shell will expand the * character and then as a result, all *.txt files are passed to your script.
If you simply do this in your script:
for i in sys.argv[1:]:
print(i)
You will see the list your program is supposed to print. To avoid this, you have a few options:
Quote your argument "/home/Documents/*.txt"
Pass only the extension part, python txt.py /home/Documents/.txt, and in your script:
def print_all(path):
path = path[:path.rfind('/')+1]+'*'+path[path.rfind('/')+1:]
for i in glob.glob(path):
print(i)
Pass two arguments, the path, and then the extension python txt.py /home/Documents/ .txt and then join them together:
print_all(sys.argv[1]+'*'+sys.argv[2])

Translate standard output memory file to string of English text as if a print command were being used

I am running a find command for a particular file that I know exists. I would like to get the path to that file, because I don't want to assume that I know where the file is located. My understanding is that I need to redirect stdout, run the command and capture the output, re-hook-up standard output, then retrieve the results. The problem comes when I retrieve the results... I can't decipher them:
import os
from cStringIO import StringIO
stdout_backup = sys.stdout #Backup standard output
stdout_output = StringIO()
sys.stdout = stdout_output #Redirect standard output
os.system("find . -name 'foobar.ext' -print") #Find a known file
sys.stdout = stdout_backup #re-hook-up standard output as top priority
paths_to_file = stdout_ouput.get_value() #Retrieve results
I find all the paths I could want, the trouble is that paths_to_file yields this:
Out[9]: '\n\x01\x1b[0;32m\x02In [\x01\x1b[1;32m\x027\x01\x1b[0;32m\x02]: \x01\x1b[0m\x02\n\x01\x1b[0;32m\x02In [\x01\x1b[1;32m\x028\x01\x1b[0;32m\x02]: \x01\x1b[0m\x02'
I have no idea what to do with this. What I wanted was something like what the print command provides:
./Work/Halpin Programs/Servers/selenium-server.jar
How do I make that output usable for opening a file? If I can get what the print command yeilds, I can open the file I want.
Please reorient the question if I am misguided. Thank you!
You cannot capture the output of a subprocess by changing sys.stdout. What you captured seems to be some ANSI escape sequences from your interactive Python interpreter (IPython?).
To get the output of an external command, you should use subprocess.check_output():
paths = subprocess.check_output(["find", ".", "-name", "foobar.ext"])
In this particular case, I usually wouldn't call an external command at all, but rather use os.walk() to find the files from right within the Python process.
Edit: Here's how to use os.walk() to find the files:
def find(path, pattern):
for root, dirs, files in os.walk(path):
for match in fnmatch.filter(files, pattern):
yield os.path.join(root, match)
paths = list(find(".", "foobar.ext"))

How to move a file with a complicated filename in python

I am trying to do $ mv <file> .. in a python script using subprocess.call(). I am able to do this on 'normal' filenames, but on certain filenames it does not work. I do not have control of the filenames that are given to the script. Here is an example:
M filename is "ITunes ES Film Metadata_10_LaunchTitles(4th Batch)_08_20_2010.XLS"
When I try and do the command directly into the python prompt and drag the file into it, this is what I get:
>>> /Users/David/Desktop/itunes_finalize/TheInventionOfLying_CSP/
ITunes\ ES\ Film\ Metadata_10_LaunchTitles\(4th\ Batch\)_08_20_2010.XLS
No such file or directory
How would I go about moving this file in a python script?
Update:
Thanks for the answers, this is how I ended up doing it:
for file in glob.glob(os.path.join(dir, '*.[xX][lL][sS]')):
shutil.move(file, os.path.join(os.path.dirname(file), os.path.pardir))
subprocess is not the best way to go here. For example, what if you're on an operating system that isn't POSIX compliant?
Check out the shutil module.
>>> import shutil
>>> shutil.move(src, dest)
If finding the actual string for the filename is hard you can use glob.glob to pattern match what you want. For example, if you're running the script/prompt from the directory with the .XLS file in question you could do the following.
>>> import glob
>>> glob.glob('*ITunes*.XLS')
You'll get a list back with all the file strings that fit that pattern.
Rather than using subprocess and spawning a new process, use shutil.move() to just do it in Python. That way, the names won't be reinterpreted and there will be little chance for error.
Spaces, parens, etc. are the shell's problem. They don't require escaping in Python provided you don't pass them to a shell.
open('*WOW!* Rock&Roll(uptempo).mp3')

Categories

Resources