Home > Back-end >  Easiest way to parse command line string to subprocess list?
Easiest way to parse command line string to subprocess list?

Time:07-02

I'm trying to figure out how to run this command using subprocess.run():

cmd = 'find / \( -path /mnt -prune -o -path /dev -prune -o -path /proc -prune -o -path /sys -prune \) -o ! -type l -type f -or -type d -printf "depth="%d/"perm="%m/"size="%s/"atime="%A@/"mtime"=%T@/"ctime"=%C@/"hardlinks"=%n/"selinux_context"=%Z/"user="%u/"group="%g/"name="%p/"type="%Y\\n'

I've put the command into a list, even removing items, etc:

cmd = [
    'find',
    '/',
    '\( -path /mnt -prune -o -path /dev -prune -o -path /proc -prune -o -path /sys -prune \)',
    '-o',
    '! -type l',
    '-type f',
    '-or',
    '-type d'
]

I've tried running the command using /bin/bash:

cmd = '/bin/bash -c find / \( -path /mnt -prune -o -path /dev -prune -o -path /proc -prune -o -path /sys -prune \) -o ! -type l -type f -or -type d -printf "depth="%d/"perm="%m/"size="%s/"atime="%A@/"mtime"=%T@/"ctime"=%C@/"hardlinks"=%n/"selinux_context"=%Z/"user="%u/"group="%g/"name="%p/"type="%Y\\n'

Doesn't matter. Everything I've tried does not work. Either I get no output at all, or it lists the files in my home directory, or I get an error, e.g.: b'find: paths must precede expression: ! -type l\nUsage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]\n'

Is there any easy way to take a command that works at the command line and just parse the string into whatever list elements subprocess.run() wants?

CodePudding user response:

Parsing With shlex.parse()

After fixing the incorrect quotes in your printf string, we get:

cmd = r'''
find / \( -path /mnt -prune -o -path /dev -prune -o -path /proc -prune -o -path /sys -prune \) -o ! -type l -type f -or -type d -printf 'depth=%d/perm=%m/size=%s/atime=%A@/mtime=%T@/ctime=%C@/hardlinks=%n/selinux_context=%Z/user=%u/group=%g/name=%p/type=%Y\\n'
'''
print(shlex.split(cmd))

...which emits an entirely correct result, and subprocess.call() works with it properly.


Building A Correct Command Line By Hand

In terms of what it looks like to do this by hand:

cmd = [
    'find', '/',
    '(',
             '-path', '/mnt',  '-prune',
       '-o', '-path', '/dev',  '-prune',
       '-o', '-path', '/proc', '-prune',
       '-o', '-path', '/sys',  '-prune',
    ')',
    '-o', '!', '-type', 'l',
    '-type', 'f',
    '-or',
    '-type', 'd',
    '-printf', 'depth=%d/perm=%m/size=%s/atime=%A@/mtime=%T@/ctime=%C@/hardlinks=%n/selinux_context=%Z/user=%u/group=%g/name=%p/type=%Y\n'
]

Note:

  • Syntactic quotes change the shell's parsing mode, they don't become part of the data. "foo" just becomes foo; "foo"bar"baz" becomes foobarbaz. So you can't/shouldn't/don't try to put those quotes into the data that Python is passing in.
  • This is true also for \(: the backslash is shell syntax. It doesn't actually become one of find's arguments, so you leave it out.
  • Any space that isn't quoted or escaped separates words; so -type f in shell is '-type', 'f', two separate words.
  • Related