Search code examples
shellxonsh

How to expand shell variable in xonsh subprocess?


I'm learning xonsh. I tried the following simple script, but it fails at zipinfo -1 $mzip_str, specifically at expanding mzip_str.

#!/usr/bin/env xonsh

from pathlib import Path

my_path = Path('/path/to/downloads/')

dir_list = my_path.glob('*.zip')
for my_zip in dir_list:
  mzip_str = str(my_zip)
  zip_dir_names = $(zipinfo -1 $mzip_str | grep -E '.*/$')
  print(zip_dir_names)

In the xonsh shell, I get these results:

➤ zip_dir_names = $(zipinfo -1 mzip_str)
zipinfo:  cannot find or open mzip_str, mzip_str.zip or mzip_str.ZIP.

➤ zip_dir_names = $(zipinfo -1 $mzip_str)
zipinfo:  cannot find or open $mzip_str, $mzip_str.zip or $mzip_str.ZIP.

➤ zip_dir_names = $(zipinfo -1 @mzip_str)
zipinfo:  cannot find or open @mzip_str, @mzip_str.zip or @mzip_str.ZIP.

➤ zip_dir_names = $(zipinfo -1 !mzip_str)
zipinfo:  cannot find or open mzip_str, mzip_str.zip or mzip_str.ZIP.

Solution

  • From the official tutorial

    The @(<expr>) operator form works in subprocess mode, and will evaluate arbitrary Python code. The result is appended to the subprocess command list. If the result is a string or bytes, it is appended to the argument list. If the result is a list or other non-string sequence, the contents are converted to strings and appended to the argument list in order. If the result in the first position is a function, it is treated as an alias (see the section on Aliases below), even if it was not explicitly added to the aliases mapping. Otherwise, the result is automatically converted to a string.

    So the relevant line should look like (Untested as I don't have this xonsh installed):

    zip_dir_names = $(zipinfo -1 @(my_zip) | grep -E '.*/$')
    

    But it sounds like $() returns a string and you probably want zip_dir_names to be a list if you want to do anything with it but print it. Something like

    zip_dir_names = [ name for name in !(zipinfo -1 @(my_zip)) if name.endsWith("/") ]
    

    might work better. Or just use the standard Python zipfile module module instead of an external program, of course.

    zip_dir_names = [ f.filename for f in ZipFile(my_zip).infolist() if f.is_dir() ]