Link to home
Start Free TrialLog in
Avatar of chipped
chippedFlag for Australia

asked on

CD to parent directory of a file path in Python

Hey people,

I'm a crappy scripter and brand new to python.

This is the code I need help with
echo Changing Directory to provided episode path... >> $CALL_DATECHANGE_LOG
echo From `pwd` >> $CALL_DATECHANGE_LOG
cd "${PROCESSED_EPISODE_PATH}"
echo To `pwd` >> $CALL_DATECHANGE_LOG
echo Invoking Periscope to original episode name $ORIGINAL_EPISODE_NAME >> $CALL_DATECHANGE_LOG
echo The final command line is: >> $CALL_DATECHANGE_LOG
echo $PERISCOPE_BIN $PERISCOPE_OPTS $PROCESSED_EPISODE_FILE_NAME >> $CALL_DATECHANGE_LOG
echo Invoking it now: >> $CALL_DATECHANGE_LOG

Open in new window


The part "PROCESSED_EPISODE_PATH" will always be "\RandomPath\Shows\ShowName\Seasonxx\episode.mkv"

I need to CD to the show name, it's always going to be two levels up, how do i do it?

Maybe I'm asking the wrong thing, later in the script I need to modify "\RandomPath\Shows\ShowName"

This script is an Extra script that is post processed after another one, the "${PROCESSED_EPISODE_PATH}" is parsed from the previous script, hence the weird request hehe

Cheers.
Avatar of HonorGod
HonorGod
Flag of United States of America image

To begin, the script shown above is not Python.  It is a shell scripting language.

What is the output of?

echo $SHELL

Do you want the Python equivalent of the script shown above?
e.g., if you have a variable containing a path name, like this:

here = r"\RandomPath\Shows\ShowName";

then you can figure out the parent using something simple like:

Example output:

current directory: \RandomPath\Shows\ShowName
parent directory: \RandomPath\Shows
grandparent dir: \RandomPath
import os;

here = r"\RandomPath\Shows\ShowName";

print "current directory:", here;

parent = '\\'.join( here.split( '\\' )[ :-1 ] );

print "parent directory:", parent;

grandpa = '\\'.join( here.split( '\\' )[ :-2 ] );

print "grandparent dir:", grandpa;

Open in new window

The way it work is that the string type includes a method called split() which takes the string, and uses the split() argument (i.e., a backslash in this case) and returns an array (list) of the substrings delimited by the specified character.  So, the result of:

here.split( '\\' )

will be an array (or list) containing:

['', 'RandomPath', 'Shows', 'ShowName']

The reason the first element is empty is because nothing precedes the initial backslash in the input string.

Given a list, use slicing to make a copy of a portion of the list.

So, given a list, we can specify that we want everything except the last two entries using:

[ :-2 ]

The result of which is, in this case:

['', 'RandomPath']

Given a list, we can combine the elements, and specify the delimiter element, using this notation:

'\\'.join( ... )

Where ... is where the list should be that will be combined into the returned string:

Does that make sense?

Avatar of pepr
pepr

It is probably better to use the os.path.split(yourFullName). You can do it twice, or you can os.path.join() the first head with '..' and then use the os.path.abspath().

See http://docs.python.org/library/os.path.html
http://docs.python.org/library/os.path.html#os.path.split
http://docs.python.org/library/os.path.html#os.path.join
http://docs.python.org/library/os.path.html#os.path.abspath
Avatar of chipped

ASKER

Doh! This is why I shouldn't script when I'm tired :P

Don't worry about the above script, here is my script so far in Python

import os, time

#path to file
file = '$1'

#Change to Grandparent Folder
fullpath = r"file";

print "current directory:", here;

parent = '\\'.join( fullpath.split( '\\' )[ :-1 ] );

print "parent directory:", parent;

grandpa = '\\'.join( fullpath.split( '\\' )[ :-2 ] );

print "grandparent dir:", grandpa;

#get current file stats
stats = os.stat(file)

#convert time to a more useable format
lastmod_date = time.localtime(stats[8])

#print the last date of modification
print(lastmod_date)

#new value to be stored in "Date Modified" file attribute
modDate = (2009, 2, 4, 20, 20, 12, 6, 39, 0)

#print new value (to compare against modified file
print(modDate)

#convert time to a value the system can handle
SysTime = time.mktime(modDate)

#print the value after conversion
print(SysTime)

#update the file attributes
os.utime('C:\\test\\time.txt', (int(time.time()), SysTime))

Open in new window


So what it does is, modify the Data Modified attribute of the Grandparent folder. I got this code off the net, and I'm not 100% sure how to use Python.

I'm stuck at:

Is Line 4 ok?

How do I use the grandparent directory we figure out on Line 15 in Line 41?

That's it I think :)
ASKER CERTIFIED SOLUTION
Avatar of pepr
pepr

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of chipped

ASKER

Thanks for that Pepr, but the file path will always be parsed by another python script. I have to use $1 to use it.

So can I make
fname = '/RandomPath/Shows/ShowName/Seasonxx/episode.mkv'

Open in new window

this into
fname = '$1'

Open in new window

Would that work alright?
The $1 is a notation used by shell scripts.

In Python, it is sys.argv[ 1 ]... for example, the output for this script, invoked like this:

python bob.py Hi

Is:

Hi
import sys;
print sys.argv[ 1 ];

Open in new window

Avatar of chipped

ASKER

Pepr, I see that your code outputs the directory that I need, but I also need to integrate the extra code from this post that changes the date modified attribute of the outputted parent folder. https://www.experts-exchange.com/questions/26852254/CD-to-parent-directory-of-a-file-path-in-Python.html?anchorAnswerId=35003243#a35003243
Avatar of chipped

ASKER

HonorGod: Oh ok, thanks.
As HonorGod said.  See also the line 5 in my previous sample that shows exactly the same -- only commented out.

Well, I did not touched the code for modification of the file time.  It is not clear to me, what time should be set to what and what is the overall purpose of that time modification.

A side note: The 'file' identifier should not be used for your variables as it is used form something else.  It may not be that much wrong; however, it may be confusing for those who know what it does name.
Avatar of chipped

ASKER

Pepr: The reason I want to modify the time of the "ShowName" directory, is so my windows media centre can see the updated show and organise the list accordingly :)
What OS are you using?
Avatar of chipped

ASKER

Windows, but apparently the date modification code will work on any OS. http://www.dreamincode.net/forums/topic/85465-python-editing-date-modified-file-attribute/
Avatar of chipped

ASKER

Hmmmmm, os.utime doesn't work on folders. So I'm going to have to create a file under the showname, then delete and recreate each time a episode is added. Then the showname dir will have an updated date modified time and Windows Media Centre will see it.
Avatar of chipped

ASKER

Pepr: I could use your script and then add a some code to create a file like "\RandomPath\Shows\ShowName\NewEpisodeMarker.txt", and if it exists, delete and recreate.

A little dodgy, but I need Windows Media Centre to be able to see "\RandomPath\Shows\ShowName" as being modified, so it can organize my shows by date.
Avatar of chipped

ASKER

Ok guys, we are in action, sort of. One minor kink to work out.

Here is the final code:
import os
import sys

#path to file from the first argument (no checking)
fname = sys.argv[1]
#fname = r'E:\\TV Shows\\How I Met Your Mother\\Season 6\\How.I.Met.Your.Mother.S06E18.720p.HDTV.X264-DIMENSION.mkv'

# Now split the path to the directory and the bare name (better to say it 
# splits the last element of the path and it does not care whether it is 
# a file name or a subdirectory name.
path, barename = os.path.split(fname)
print path
print barename

parent = os.path.normpath(os.path.join(path, '..'))

print "parent directory:", parent

# Create file, delete and recreate it if it exists. This will
# change the date modified status of the Show Name folder
# so you can view your show list by date order.
if os.path.isfile(parent + "/" + 'DateMarker.log'):
    os.remove(parent + "/" + 'DateMarker.log')
logfile = open(parent + "/" + 'DateMarker.log', 'w')
logfile.write('Episode ' + fname + ' has been created. This is a marker file, to change the date modified status of the show name.')
logfile.close

Open in new window


And here is the error I get when the script is called by the other python script:
Mar-02 11:44:50 INFO     POSTPROCESSER :: Unable to run extra_script: [Error 193] %1 is not a valid Win32 application
Mar-02 11:44:50 INFO     POSTPROCESSER :: Executing command ['DateM.py', u'E:\\TV Shows\\The Mentalist\\Season 01\\the.mentalist.s01e10.720p.hdtv.x264-ctu.mkv', u'E:\\Complete\\The Mentalist S01E10 720p HDTV x264 CTU\\the.mentalist.s01e10.720p.hdtv.x264-ctu.mkv', '82459', '1', '10', '2008-12-16']

Open in new window


I tried running the script with fname manually point to the parsed file path and it works fine but with "fname = sys.argv[1]" it craps out that error.
For the line 1 in the error log: The %1 is interpreted by the Windows cmd (batch interpreter) as the value of the first argument.  It is likely that you passed it so that it was not interpreted.

I am not sure if the line 2 is OK. The reason is that the list shows a mixture of both bare, old (Python 2.x) strings and of unicode strings.  It may work if it is anticipated byt the implementation that executes the comman, but I am not sure here.

If the line 6 were used, it should not double backslashes or the r (raw string literal) prefix should be removed. Better to use normal slashes.

I suggest to use os.path functions whenever you work with paths.  You will get used to and you will really appreciate it later (means less errors and problems later) -- see the lines 22 to 24.

There is the principle called DRY in programming.  It means "Don't Repeat Yourself", e.g. "do not write the same thing twice".  The reason is that when modified, you have to modify on more places.  But the main reason is that you want to be lazy as the lazines is the father of progress (the same way as "Repeating is the mother of wisdom" ;) .

The line 26 does not do what you expect.  It literally does nothing as you forgot to add the parentheses -- the method is not called.

After putting the last three together, the lines should look like that (I cannot control the line numbering -- it should be started from 22):

logname = os.path.join(parent, 'DateMarker.log')
if os.path.isfile(logname):
    os.remove(logname)
logfile = open(logname, 'w')
logfile.write('Episode ' + fname + ' has been created. This is a marker file, to change the date modified status of the show name.')
logfile.close()

Open in new window


But there is one more enhancement that makes the code shorter.  When you open the file using the mode 'w', the existing content is overwritten.  This way there is no need to remove the logfile first.

logname = os.path.join(parent, 'DateMarker.log')
logfile = open(logname, 'w')
logfile.write('Episode ' + fname + ' has been created. This is a marker file, to change the date modified status of the show name.')
logfile.close()

Open in new window


As the logname is not repeated now, the os.path.join() could be inserted directly to the open() command.  I prefer the separation as it is more readable and you never know how the code is to be modified later.  But you can combine the 'parent' command from your line 17 as it is probably used only to get the logfile name (probably no need to os.path.normpath()):

logname = os.path.join(path, '..', 'DateMarker.log')

Open in new window

You should also explain how do you want to launch the DateM.py with the arguments.  There are more ways to do that, but the most natural is to define the function in the DateM.py and use the DateM.py as module:

import DateM
...
DateM.myFunction(u'E:\\TV Shows\\The Mentalist\\Season 01\\the.mentalist.s01e10.720p.hdtv.x264-ctu.mkv', 
                 u'E:\\Complete\\The Mentalist S01E10 720p HDTV x264 CTU\\the.mentalist.s01e10.720p.hdtv.x264-ctu.mkv', 
                 '82459', '1', '10', '2008-12-16')

Open in new window

Avatar of chipped

ASKER

This is the code that runs my script, I think it might might not be running my script properly
    def _run_extra_scripts(self, ep_obj):
        for curScriptName in sickbeard.EXTRA_SCRIPTS:
            script_cmd = shlex.split(curScriptName) + [ep_obj.location, self.file_path, str(ep_obj.show.tvdbid), str(ep_obj.season), str(ep_obj.episode), str(ep_obj.airdate)]
            self._log(u"Executing command "+str(script_cmd))
            self._log(u"Absolute path to script: "+ek.ek(os.path.abspath, script_cmd[0]), logger.DEBUG)
            try:
                p = subprocess.Popen(script_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=sickbeard.PROG_DIR)
                out, err = p.communicate()
                self._log(u"Script result: "+str(out), logger.DEBUG)
            except OSError, e:
                self._log(u"Unable to run extra_script: "+str(e).decode('utf-8'))

Open in new window


I think this is causing the problem, I don't think the subprocess is being run properly.
p = subprocess.Popen(script_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=sickbeard.PROG_DIR)

Open in new window

Any ideas?
When using the subprocess.Popen(), you prescribe the execution of some executable file.  The .py file is just a text file... unless you work in UNIX-like environmend and chmod the file as executable. In the case, you have to use the #! magical line to define what interpreter will be used to launch it.

Are all of yours called script the Python scripts?  If yes, or you have to call the Python intepretere and pass the DateM.py as the first argument, or you have to mark the DateM.py as executable (UNIX), or you can use the execfile() function (http://docs.python.org/library/functions.html#execfile). BUT...

I still suggest to import the Python module and call the function from inside.  Can you show the fragment of your DateM.py?
Avatar of chipped

ASKER

I'm running on windows, that code that runs my script, is from another script which is not mine. It just calls my script to do some post processing after it.

All sorts of different scripts from all different Operating Systems are launched from there, some people have shell, batch, python, Windows, Mac, Linux etc

What is a fragment?
I see. Then you have to have all kind of interpreters on your Windows, like shell, cmd, python.exe... And you have to launch that interpreter and pass it the script as the first argument and the rest of arguments as the other ones.  You have to detect what interpreter should be used for what script. Does it make sense -- just from the abstract point of view? Do I understand it well?
Avatar of chipped

ASKER

Yep
Avatar of chipped

ASKER

Ok, you are correct. This guy has the exact same problem as me, and he fixed it by implementing what your suggesting.

http://stackoverflow.com/questions/912830/using-subprocess-to-run-python-script-on-windows

I have submitted an issue to the developer of SickBeard << Thats the program based on python, the one that launches my script to post process.
Avatar of chipped

ASKER

As a temporary solution, I want to run another script like a batch or vbscript to write to passed file name path to a log/text file and then modify the post process script to read the file path from there.

Should I create another thread or can you help me?
Let's do it here... in steps. The final goal will be to generate a batch file that will call the wanted script. Try the following separate script:

b.py
import shlex
import sys

scriptName = 'test.py'                     
lst = shlex.split(scriptName) + sys.argv[1:]
print lst

Open in new window


And try to call it from the command line:

C:\tmp\___python\chipped\Q_26852254>python "b.py" "a" "b" "c"
['test.py', 'a', 'b', 'c']

Open in new window


Notice I did wrapped the arguments (including the b.py) to double quotes.  This is what the cmd expects when the argument contains say spaces in path or so.  It is just more reliable to us that.

The shlex.split() in simple case just converts the string (no arguments) with the script name to the list containing the same string.  The sys.argv is the list of arguments passed to the Python script -- here only the a, b, c.  The + operator joins the two lists into one.
Avatar of chipped

ASKER

Oh yeah, that works fine.
Now try this:

c.py
import os
import shlex
import subprocess
import sys

scriptName = 'test.py'                       # this is the script to be called
lst = shlex.split(scriptName) + sys.argv[1:]

# Detect the kind of script and prepend the interpreter accordingly.
name, ext = os.path.splitext(scriptName)
if ext.lower() == '.py':
    lst.insert(0, 'python.exe')  

# Wrap all elements into double quotes.
lst = ['"' + s + '"' for s in lst]           
print lst

# Generate the aux.bat batch file.
cmd = ' '.join(lst)
print cmd

batchName = 'myBatch.bat'
f = open(batchName, 'w')
f.write('echo The batch started\n')
f.write(cmd)
f.close()

# Call the batch via the subprocess.Popen
p = subprocess.Popen(batchName, 
                     stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                     shell=True)   
out, err = p.communicate()

print 'Output:'
print out
print 'Error:'
print err

Open in new window


It prints on my console:

C:\tmp\___python\chipped\Q_26852254>python "c.py" "a a a" "b b b" "c c c"
['"python.exe"', '"test.py"', '"a a a"', '"b b b"', '"c c c"']
"python.exe" "test.py" "a a a" "b b b" "c c c"
Output:

C:\tmp\___python\chipped\Q_26852254>echo The batch started
The batch started

C:\tmp\___python\chipped\Q_26852254>"python.exe" "test.py" "a a a" "b b b" "c c c"
test ['test.py', 'a a a', 'b b b', 'c c c']

Error:
None

Open in new window

test.py
Avatar of chipped

ASKER

Ok working fine.
Avatar of chipped

ASKER

To be honest, I've kinda lost you. I have no idea what "c.py" does and I'm not even sure how/if it relates to "b.py" :S
c.py, b.py -- they are just names of the samples.  Consider them a different scripts or different versions of the script. The c.py is just the next step of enhancing the b.py.  I use the name mainly because I usually capture the output on console where the name is visible.

The c.py shows the process of how your original subprocess.Popen() arguments can be converted to the string that can be written into the auxiliary batch file.  Then the subprocess.Popen() can launch that auxiliary batch file (fixed name) instead of unsuccessfull launch of the earlier script.

This way you get the intermediate step (the batch file) that solves some things like searching for the executable in the PATH (this is not done via subprocess.Popen() -- you have to explicitly pass the full name of the executable).  Also, there probably will be no problem with launching a batch file.  All the problems can be pre-solved when generating the batch file.  You can also easily test the batch file when running manually.
In your case, the

lst = shlex.split(scriptName) + sys.argv[1:]

# Detect the kind of script and prepend the interpreter accordingly.
name, ext = os.path.splitext(scriptName)
if ext.lower() == '.py':
    lst.insert(0, 'python.exe')

Open in new window


should be replaced by

script_cmd = shlex.split(curScriptName) + [ep_obj.location, self.file_path, str(ep_obj.show.tvdbid), 
                                           str(ep_obj.season), str(ep_obj.episode), str(ep_obj.airdate)]

# Detect the kind of script and prepend the interpreter accordingly.
name, ext = os.path.splitext(curScriptName)
if ext.lower() == '.py':
    script_cmd.insert(0, 'python.exe')

# Wrap all elements into double quotes.
lst = ['"' + s + '"' for s in script_cmd]           

# Generate the aux.bat batch file.
cmd = ' '.join(lst)

batchName = 'myBatch.bat'
f = open(batchName, 'w')
f.write('echo The batch started\n')
f.write(cmd)
f.close()

Open in new window


and put into your _run_extra_scripts() code... And then the batchName should be launched without other arguments by subprocess.Popen()

Anyway, it is rather complicated. Do not you want to modify the original code?
Avatar of chipped

ASKER

Wait, I don't want to modify the code that calls my post process script, that's not my code, it's a piece of code from SickBeard (that's the program, the one that calls my script)

I want to work around it, then run it normally when the developer gets around to fixing the bug.
Avatar of chipped

ASKER

The only code that mine is https://www.experts-exchange.com/M_4593526.html

The code that calls that script is not mine, its part of a program called SickBeard.

Sorry about the confusion.
Avatar of chipped

ASKER

Do you get me now?
I see. How the sickbeard.EXTRA_SCRIPTS is defined?  In other words, how do you pass the name of your script to the sickbeard?
If you somewhere wrote a line like:

DateM.py

Open in new window


then you should modify it to something like:

c:/Python27/python.exe DateM.py

Open in new window


Warning! It must use normal slashes in the path as the shlex.split() removes the backslashes.

Then the line will be split to two elements in the script_cmd, and the SickBeard will subprocess.Popen() the command like

['c:/Python27/python.exe', 'DateM.py', u'E:\\TV Shows\\The Mentalist\\Season 01\\the.mentalist.s01e10.720p.hdtv.x264-ctu.mkv', u'E:\\Complete\\The Mentalist S01E10 720p HDTV x264 CTU\\the.mentalist.s01e10.720p.hdtv.x264-ctu.mkv', '82459', '1', '10', '2008-12-16']

Open in new window


and this should work in your case.
Avatar of chipped

ASKER

No, I don't want to pass any info to sickbeard.

I just want to use the file name paths that sickbeard passes to the EXTRA_SCRIPTS.

We have made the script for that above.

But SickBeard EXTRA_SCRIPT does not run the python script properly, so our script is doing nothing at the moment.

As a temporary fix, I want to run an SickBeard EXTRA_SCRIPT that will write the file name paths to a log file.

Then our script can read the file name paths from that and do it's thing.

Do you get me now?
I am confused.  Could you explain how the following arguments get into the SickBeard?

['DateM.py', u'E:\\TV Shows\\The Mentalist\\Season 01\\the.mentalist.s01e10.720p.hdtv.x264-ctu.mkv', u'E:\\Complete\\The Mentalist S01E10 720p HDTV x264 CTU\\the.mentalist.s01e10.720p.hdtv.x264-ctu.mkv', '82459', '1', '10', '2008-12-16']

Open in new window


Is he DateM.py your script?
Avatar of chipped

ASKER

That line is generates by SicBeard in this script:
    def _run_extra_scripts(self, ep_obj):
        for curScriptName in sickbeard.EXTRA_SCRIPTS:
            script_cmd = shlex.split(curScriptName) + [ep_obj.location, self.file_path, str(ep_obj.show.tvdbid), str(ep_obj.season), str(ep_obj.episode), str(ep_obj.airdate)]
            self._log(u"Executing command "+str(script_cmd))
            self._log(u"Absolute path to script: "+ek.ek(os.path.abspath, script_cmd[0]), logger.DEBUG)
            try:
                p = subprocess.Popen(script_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=sickbeard.PROG_DIR)
                out, err = p.communicate()
                self._log(u"Script result: "+str(out), logger.DEBUG)
            except OSError, e:
                self._log(u"Unable to run extra_script: "+str(e).decode('utf-8'))

Open in new window

OK. What is the sickbeard.EXTRA_SCRIPTS?  Where it gets its values?
Avatar of chipped

ASKER

SickBeard is a program which actually monitors files that have been downloaded, then moves them into their folders.

It passes the file name path and some other values to the extra script, so the user can do some post processing of their own.
I think I understand that.  But how you tell the SickBeard to use the extra script?  Is the DateM.py your extra script?  Or is it the standard part of the SickBeard?
Avatar of chipped

ASKER

Yep, DateM.py is my script.

There is a config.ini where I put the path to my script.
Can you show the content of that part of the config.ini?
Avatar of chipped

ASKER

extra_scripts = DateM.py

Open in new window

Avatar of chipped

ASKER

Ok solved, hell yeah!

Used a batch script to call my python script properly, and pass the info.
c:\Python27\python.exe c:\SickBeard\DateM.py %1 %2 %3 %4 %5

Open in new window


And this is the code I used to place marker file in the TV Show folder, so that windows sees the TV Show as modified.
import os
import sys

#path to file from the first argument (no checking)
fname = sys.argv[1]
#fname = r'E:\\TV Shows\\How I Met Your Mother\\Season 6\\How.I.Met.Your.Mother.S06E18.720p.HDTV.X264-DIMENSION.mkv'
print fname

# Now split the path to the directory and the bare name (better to say it 
# splits the last element of the path and it does not care whether it is 
# a file name or a subdirectory name.
path, barename = os.path.split(fname)
print path
print barename

parent = os.path.normpath(os.path.join(path, '..'))
print "parent directory:", parent

# Create file, delete and recreate it if it exists. This will
# change the date modified status of the Show Name folder
# so you can view your show list by date order.
logname = os.path.join(parent, 'DateMarker.log')
if os.path.isfile(logname):
    os.remove(logname)
logfile = open(logname, 'w')
logfile.write('Episode ' + fname + ' has been created. This is a marker file, to change the date modified status of the show name.')
logfile.close()

Open in new window

Avatar of chipped

ASKER

Thank you so much guys, especially Pepr.