pythonlinuxshopt

Why does shopt fail when called from python?


# shopt -s extglob

# python3
Python 3.4.0 (default, Sep  8 2015, 23:36:36) 
[GCC 4.7.3] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from subprocess import check_call
>>> check_call(['shopt', '-s', 'extglob'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.4/subprocess.py", line 552, in check_call
    retcode = call(*popenargs, **kwargs)
  File "/usr/lib/python3.4/subprocess.py", line 533, in call
    with Popen(*popenargs, **kwargs) as p:
  File "/usr/lib/python3.4/subprocess.py", line 848, in __init__
    restore_signals, start_new_session)
  File "/usr/lib/python3.4/subprocess.py", line 1446, in _execute_child
    raise child_exception_type(errno_num, err_msg)
FileNotFoundError: [Errno 2] No such file or directory: 'shopt'

shopt doesn't appear to be in my path but bash does:

# echo $PATH | grep shopt
# whereis shopt
# whereis bash
bash: /bin/bash /etc/bash.bashrc /usr/share/man/man1/bash.1.gz

Solution

  • If you want to "rm all but two files in a directory", you can do that directly from Python without invoking a shell. This program removes all of the files in /tmp/r, except for two that I want to keep.

    #!/usr/bin/python3
    
    import os
    
    keepers = ['safe', 'also_safe.txt']
    os.chdir('/tmp/r')
    
    for filename in os.listdir('.'):
        if filename not in keepers:
            print('Removing %s' % (filename,))
            os.remove(filename)
    

    And, for fun, here is a combined module and script that provide the same functionality:

    #!/usr/bin/python3
    
    import os
    import argparse
    import sys
    
    def safe_remove(dirs, exclude, verbose, dry_run):
        for directory in dirs:
            if verbose or dry_run:
                print("Checking directory '%s'" % (directory,))
            for filename in os.listdir(directory):
                if filename not in exclude:
                    filename = os.path.join(directory, filename)
                    if verbose or dry_run:
                        print('rm %s'%filename)
                    if not dry_run:
                        os.remove(filename)
    
    if __name__ == "__main__":
        parser = argparse.ArgumentParser(description='Remove files, with exceptions')
        parser.add_argument('-x', '--exclude',
                            metavar='FILE',
                            default=[],
                            nargs='+',
                            help='files to exclude')
        parser.add_argument('dirs',
                            metavar='DIR',
                            nargs='+',
                            help='directories to clean')
        parser.add_argument('-v', '--verbose',
                            action='store_true',
                            help='Print actions')
        parser.add_argument('-n', '--dry-run',
                            action='store_true',
                            help='Print, but do not perform, actions')
        args = parser.parse_args()
        safe_remove(**vars(args))