"Wildcard has no match" exception is unergonomic
What new feature should Elvish have?
I see that the exception throwing by default when wildcards have no match was added some time ago in response to a real issue.
However, the behaviour we have now is rather awkward. For example, when wanting to delete any file matching some set of wildcards:
> ls
a ab abc
> rm -f ab* bc*
Exception: wildcard has no match
[tty 38]:1:11-13: rm -f ab* bc*
> ls
a ab abc
> rm -f ab*[nomatch-ok] bc*[nomatch-ok]
> ls
a
Bash is much more convenient:
> bash
$ ls
a ab abc
$ rm -f ab* bc*
$ ls
a
Could there be room for a rethink here?
Output of "elvish -version"
0.21.0
Code of Conduct
- [X] I agree to follow Elvish's Code of Conduct.
Here is a more specific proposal to mitigate the awkwardness of the "wildcard has no match" exception, looping in @muesli who raised the original issue, and @xiaq obviously who is the design authority.
I suggest that the nomatch-ok global modifier for wildcards is a mistake, and should instead be the default. I noticed that it was introduced very quickly (perhaps rather hastily?) in response to that specific situation where ls lists everything if given no arguments. I think that could be solved in a more elegant way.
Suppose that the previous behaviour were re-instated, that is wildcards with no matches simply resolve to empty. This makes the awkwardness I describe above simply disappear. I suspect in general it is more convenient. It certainly seems more elegant to me, whereas the nomatch-ok modifier seems somewhat of a wart.
So then, what to do about ls *.pdf listing all files if there are no matches. That is a problem if the file list is piped somewhere, e.g. to xargs with some action that may be dangerous. I suggest that not be the idiomatic way of passing matching filenames through a pipe.
So perhaps for example instead of this unsafe practice:
> ls *.pdf | xargs rm -f
we should instead be adopting this:
> put *.pdf | to-lines | xargs rm -f
which could be made slicker, obviously.
My point is, wildcard expansion is such a fundamental part of shell behaviour that it needs to be conceptually clean, and what we have today falls short in my view.
Do @muesli or @xiaq have an opinion about this?
I am happy to implement something, but that seems a bit premature without first agreeing the desired behaviour!
I agree with the desired behavior, and that's what I would do if we were starting from scratch.
But because we're not starting from scratch, there are some things to consider.
We do need an easy way to get the old behavior back. We can have a command that asserts the arity of inputs - like how one asserts an arity of one, we can have at-least-one to assert an arity of 1 or more. But a general purpose one is more desirable, and we can borrow the slicing syntax to express range. So:
put a | must-have 1 # ok, passes through "a"
put | must-have 1 # throws exception
put a b | must-have 1 # throws exception
put a | must-have 1.. # ok, passes through "a"
put a b | must-have 1.. # ok, passes through "a" and "b"
put | must-have 1.. # throws exception
This can follow the convention for value inputs, so you can also do:
must-have 1.. [a] # outputs "a"
must-have 1.. [a b] # outputs "a" and "b"
must-have 1.. [] # throws exception
And this form can be useful for getting the old behavior:
echo (must-have 1.. [*.c])
Another thing to consider is a migration path. For code that is using *[nomatch-ok], the easy thing to do is to keep the modifier for a while, but make it a no-op and issue a deprecation warning.
For code that is using plain * with the intention that an exception should be thrown when there are no matches, it's a bit more tricky. They can be changed to use the new must-have command, but this breaks compatibility with previous release, which didn't have the must-have command. So the safe thing to do is to introduce the must-have command first in release N, and then change the behavior of wildcards in the next release. (The implicit assumption being that people might be one release behind, but not two.) But maybe there isn't enough code taking advantage of this behavior for them to be worth the worry.
Actually I thought of a partial solution which would make things a lot better. That is, only failing a glob match if all the globs in a command invocation fail.
So rm -f *.jpg *.png wouldn't fail in the absence of any *.jpg match for example. But rm wouldn't be invoked if there were no matches at all, instead we would get the no-matches exception.
I think that's simple and backwards compatible and an improvement.