pythonfabricshlex

How do I reliably use Fabric's cd() context manager


I'm trying to run a find command in a directory on a remote system. Fabric changes directory sometimes but sometimes it fails, depending on whether the path contains parentheses or spaces and whether I use shlex.quote() or not. What is the correct way to handle this?

My code is basically this:

from shlex import quote
from fabric import Connection

with Connection(remote_login) as c:
    with c.cd(quote(node.src)):    # Condition 1
    # with c.cd(node.src):         # Condition 2
        result = c.run(r"nice find -maxdepth 1 -type f -printf '%f\n'", echo=True)

If I use Condition 1, it succeeds when the path contains parens. Fabric generates this line in that case:

# Fabric output success with parens in path
cd '/data/PixelSizeTestRuby105mm(Zoom248.5mm)' && nice find -maxdepth 1 -type f -printf '%f\n'

but it fails when the path contains spaces because the spaces are escaped but the path is also quoted, rather than just one or the other.

# Fabric output failure for spaces in path
cd '/data/Crystal\ Bending\ Test/Bending0' && nice find -maxdepth 1 -type f -printf '%f\n'
sh: line 0: cd: /data/Crystal\ Bending\ Test/Bending0: No such file or directory

If I instead use Condition 2, it fails for the first path and succeeds for the second.

# Fabric output failure for parens in path
cd /data/PixelSizeTestRuby105mm(Zoom248.5mm) && nice find -maxdepth 1 -type f -printf '%f\n'
sh: -c: line 0: syntax error near unexpected token `('
sh: -c: line 0: `cd /data/PixelSizeTestRuby105mm(Zoom248.5mm) && nice find -maxdepth 1 -type f -printf '%f\n''

Solution

  • This is a bug in the Invoke implementation. It simply does not perform correct shell argument escape for the paths in cd.

    As a quick fix, you could manually escape the parentheses in your path by adding a backslash in front. Using shlex.quote won’t work, as you’ve noticed yourself. Ideally the Invoke implementation should be fixed to use shlex.quote internally, rather than the ad-hoc, buggy manual escape it currently performs.