os.path.join - can I get consistency between Windows and Cygwin?
I would quite like a set of filename components that will give me consistent and "nice-looking" filenames on 开发者_JAVA百科both Windows and Cygwin. Here's what I've tried:
Input Windows Cygwin
1 os.path.join('c:', 'foo', 'bar') c:foo\bar c:/foo/bar
2 os.path.join('c:\\', 'foo', 'bar') c:\foo\bar c:\/foo/bar
3 os.path.join('c:/', 'foo', 'bar') c:/foo\bar c:/foo/bar
1 isn't what I want on Windows, I do want an absolute path, not relative to the current directory.
2 and 3 both work, but are not (I hereby define) "nice-looking" since they mix up forward and backward slashes on one platform or the other. My error and logging messages will be more readable if I can avoid this.
Option 4 is to define myself a driveroot
variable equal to c:\
on Windows and /cygdrive/c
on Cygwin. Or a function taking a drive letter and returning same. But I'd also prefer to avoid per-platform special cases between these two.
Can I have everything I want (join identical path components, to give a result that refers to the same absolute path on both platforms, and doesn't mix path separators on either platform)? Or do I have to compromise somewhere?
[Edit: in case it helps, the main use case is that c:\foo
is a path that I know about at configuration time, whereas bar
(and further components) are computed later. So my actual code currently looks a bit more like this:
dir = os.path.join('c:\\', 'foo')
# some time later
os.path.join(dir, 'bar')
That's using option 2, which results in "nice" reporting of filenames in Windows, but "not nice" reporting of filenames in Cygwin. What I want to avoid, if it's possible, is:
if this_is_cygwin():
dir = '/cygdrive/c/foo'
else:
dir = 'c:\\foo'
]
Edit: David pointed out that my understanding of 'current directory' on Windows is wrong. Sorry. However, try using os.path.abspath
to get nice pretty paths, or os.path.normpath
if you don't need absolute paths.
In method 1,
os.path.join('c:', 'foo', 'bar')
The output, c:foo\bar
, is correct. This means the path foo\bar
below the current directory on c:
, which is not the same thing as the root directory. os.path.join
is doing exactly what you tell it to, which is to take c:
(the current directory on drive c) and add some extra bits to it.
In method 2,
os.path.join(r'c:\', 'foo', 'bar')
The output for Cygwin is correct, because in Cygwin, c:\
is not the root of a drive.
What you want is the os.abspath
function. This will give you the absolute, normalized version of the path that you give to it.
However: You won't get the same string on Cygwin and Windows. I hope you're not looking for that.
At work, I have to deal with pretty random combinations of Windows without Cygiwn, Cygwin with non-Cygwin Python, Cygwin with Cygwin Python, and Unix. I haven't found a way of coping with this stuff that I'm particularly proud of, but the least hateful approach I've found so far is to always use what Cygwin calls a "mixed style" path on Windows. That's a Windows style path, but with forward slashes instead of backslashes. E.g., c:/foo/bar.txt. It also avoids a lot of gotchas, such as Cygwin bash shells seeing "\" as an escape character. Sadly, this means missing out on a lot of Python's built-in path manipulation utilities and doing things the hard way.
I'm don't have access to a machine with Python and Cygwin on it ATM, so I can't test the code snippets below. I apologize for any errors...
#Combine a path
path = '/'.join([ 'c:', 'foo', 'bar'])
#Split it back apart
pieces = path.split('/')
When in doubt, try to call Cygwin's cygpath utility. A lot of weird edge cases pop up in Cygwin, such as the fact that /cygdrive/d/ == d:\ and yet /cygdrive/d/../../ == c:\cygwin (or wherever you have Cygwin installed). Also remember that backslashes are used as escape characters in Unix style paths, such as /cygdrive/c/Documents\ and\ Settings. Cygpath does an astonishing job of taking care of these, and if it's not available it's generally safe to assume that your weird edge cases don't exist.
import sys
import subprocess
#Buncha code here ...
#We got somepath from somewhere, and don't know what format it's in.
try:
somepath = subprocess.check_output(['cygpath', '-m', somepath])
except subprocess.CalledProcessError:
#Cheap attempt at coping with the possibility that we're in Windows, but cygpath isn't available.
if sys.platform.startswith('win32'):
mypath = somepath.replace('\\', '/')
else:
mypath = somepath
#Now we can assume somepath is using forward slashes for delimiters.
Some Windows commands get confused if you pass them a Windows style path, and some Cygwin commands get confused if you pass in a Windows or a mixed style path. For instance, rsync can get confused by "c:/foo/bar.txt" because that looks like you're trying to specify "/foo/bar.txt" on a remote computer named "c". When you're calling one of these finicky Windows or Cygwin programs, use cygpath to make it happy. If you're calling a finicky Windows program and cygpath isn't available, try the ghetto "winpath = mypath.replace('/', '\')" approach. I think that can fail if you're converting a Unix style path and don't have Cygwin available, but hopefully if you don't have Cygwin available you don't have any Unix style paths on Windows to start with...
精彩评论