Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support empty list in arguments list? #279

Closed
joshma opened this issue Sep 29, 2015 · 7 comments
Closed

Support empty list in arguments list? #279

joshma opened this issue Sep 29, 2015 · 7 comments

Comments

@joshma
Copy link

joshma commented Sep 29, 2015

Was hoping to better understand why there's a warning when you try eg

sh.echo('foo', [], 'bar')
__main__:1: UserWarning: Empty list passed as an argument to '/bin/echo'. If you're using glob.glob(), please use sh.glob() instead.

My use case is I want to pass in options only sometimes:

options = [] if condition else ['-v', 'option']
sh.mycommand('foo', options, 'bar')

Right now this warns - is there a better way to accomplish my use case?

@amoffat
Copy link
Owner

amoffat commented Sep 29, 2015

In [4]: glob.glob("*.fjoae")
Out[4]: []

In [5]: sh.glob("*.fjoae")
Out[5]: '*.fjoae'

A glob.glob that doesn't expand to anything is an empty list, which make commands have misleading output or error messages when people use it with sh commands. For example:

sh.ls(glob.glob("*.fjoae"))

Will print the contents of your home directory, because you've effectively called ls with no args. Other commands behave more unexpectedly.

sh.glob just returns the pattern you passed in, if it would expand to an empty list, so your error message would be like:

/bin/ls: cannot access *.fjoae: No such file or directory

Throwing a warning on an empty list isn't the greatest solution in the world, but it solves more problems that it causes. For your case, you should probably do something like:

condition = True
options = [] if condition else ['-v', 'option']
options.append("bar")
sh.mycommand('foo', *options)

Or you can suppress warnings. Or if you have other ideas on how to better handle the glob situation, we can talk those through too.

@joshma
Copy link
Author

joshma commented Sep 29, 2015

Hm, that's rough. What if glob returned a subclass of list that sh.Command knows about? It could treat an empty globlist with a warning.

@amoffat
Copy link
Owner

amoffat commented Sep 29, 2015

You mean monkey-patching glob.glob? I'm not totally opposed to it actually

@joshma
Copy link
Author

joshma commented Sep 29, 2015

Ah, totally forgot that glob was in stdlib. Monkey-patching scares me, seems like a lot of mess for somewhat of an edge case.

I think my main issue is that the warning is pretty confusing if you're not using glob - maybe it can just be rewritten to indicate you have nothing to worry about if you're not using it / if you're not doing the equivalent of glob.

On the other hand, I'd personally prefer if it just didn't warn, especially since the docs recommend using sh.glob...

@amoffat
Copy link
Owner

amoffat commented Sep 30, 2015

idk, I'm kind of liking the monkey patch idea, and it's all your fault :)

I'm thinking something like:

import glob

old_glob = glob.glob

def patched_glob(*args, **kwargs):
    expanded = old_glob(*args, **kwargs)
    expanded._sh_glob_list = True
    return expanded

glob.glob = patched_glob

then the sh logic becomes: if list is empty and has _sh_glob_list attribute, output warning, otherwise use list as normal. i honestly don't see it as too terrible, and any biting it could do would only be related to someone somehow getting an empty list from glob that doesn't have _sh_glob_list on it. but they would have to jump through hoops to get that, like copying the list into a new list, or obtaining a reference to glob.glob before it is patched

I know if I were to remove the warning entirely, I would get people opening issues again that their glob.glob() code isn't working right.

@joshma
Copy link
Author

joshma commented Sep 30, 2015

It's a bit magical - I'd get confused if I thought about it and tried to figure out why an empty list from glob.glob() was being treated differently.

However, assuming it's more common that users of the library get bitten by an empty array, since that doesn't really mirror shell behavior (eg ls *.py), I can see the argument for treating glob.glob() as using that sort of construct and expecting the same shell-like behavior.

At the risk of supporting monkey-patching in public, I'm okay with the glob patching as discussed.

@amoffat amoffat added the 1.2 label Nov 4, 2015
@amoffat
Copy link
Owner

amoffat commented Oct 10, 2016

Monkey patching glob solution in release-1.2 branch, which should fix the original issue when it goes out

0-wiz-0 added a commit to NetBSD/pkgsrc-wip that referenced this issue Dec 12, 2016
*   added `_out` and `_out_bufsize` validator [#346](amoffat/sh#346)
*   bugfix for internal stdout thread running when it shouldn't [#346](amoffat/sh#346)

*   regression bugfix on timeout [#344](amoffat/sh#344)
*   regression bugfix on `_ok_code=None`

*   further improvements on cpu usage

*   regression in cpu usage [#339](amoffat/sh#339)

*   fd leak regression and fix for flawed fd leak detection test [#337](amoffat/sh#337)

*   support for `io.StringIO` in python2

*   added support for using raw file descriptors for `_in`, `_out`, and `_err`
*   removed `.close()`ing `_out` handler if FIFO detected

*   composed commands no longer propagate `_bg`
*   better support for using `sys.stdin` and `sys.stdout` for `_in` and `_out`
*   bugfix where `which()` would not stop searching at the first valid executable found in PATH
*   added `_long_prefix` for programs whose long arguments start with something other than `--` [#278](amoffat/sh#278)
*   added `_log_msg` for advanced configuration of log message [#311](amoffat/sh#311)
*   added `sh.contrib.sudo`
*   added `_arg_preprocess` for advanced command wrapping
*   alter callable `_in` arguments to signify completion with falsy chunk
*   bugfix where pipes passed into `_out` or `_err` were not flushed on process end [#252](amoffat/sh#252)
*   deprecated `with sh.args(**kwargs)` in favor of `sh2 = sh(**kwargs)`
*   made `sh.pushd` thread safe
*   added `.kill_group()` and `.signal_group()` methods for better process control [#237](amoffat/sh#237)
*   added `new_session` special keyword argument for controlling spawned process session [#266](amoffat/sh#266)
*   bugfix better handling for EINTR on system calls [#292](amoffat/sh#292)
*   bugfix where with-contexts were not threadsafe [#247](amoffat/sh#195)
*   `_uid` new special keyword param for specifying the user id of the process [#133](amoffat/sh#133)
*   bugfix where exceptions were swallowed by processes that weren't waited on [#309](amoffat/sh#309)
*   bugfix where processes that dupd their stdout/stderr to a long running child process would cause sh to hang [#310](amoffat/sh#310)
*   improved logging output [#323](amoffat/sh#323)
*   bugfix for python3+ where binary data was passed into a process's stdin [#325](amoffat/sh#325)
*   Introduced execution contexts which allow baking of common special keyword arguments into all commands [#269](amoffat/sh#269)
*   `Command` and `which` now can take an optional `paths` parameter which specifies the search paths [#226](amoffat/sh#226)
*   `_preexec_fn` option for executing a function after the child process forks but before it execs [#260](amoffat/sh#260)
*   `_fg` reintroduced, with limited functionality.  hurrah! [#92](amoffat/sh#92)
*   bugfix where a command would block if passed a fd for stdin that wasn't yet ready to read [#253](amoffat/sh#253)
*   `_long_sep` can now take `None` which splits the long form arguments into individual arguments [#258](amoffat/sh#258)
*   making `_piped` perform "direct" piping by default (linking fds together).  this fixes memory problems [#270](amoffat/sh#270)
*   bugfix where calling `next()` on an iterable process that has raised `StopIteration`, hangs [#273](amoffat/sh#273)
*   `sh.cd` called with no arguments no changes into the user's home directory, like native `cd` [#275](amoffat/sh#275)
*   `sh.glob` removed entirely.  the rationale is correctness over hand-holding. [#279](amoffat/sh#279)
*   added `_truncate_exc`, defaulting to `True`, which tells our exceptions to truncate output.
*   bugfix for exceptions whose messages contained unicode
*   `_done` callback no longer assumes you want your command put in the background.
*   `_done` callback is now called asynchronously in a separate thread.
*   `_done` callback is called regardless of exception, which is necessary in order to release held resources, for example a process pool
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants