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
A better test builtin: is #6124
Comments
We need to be careful to not redo You've already left out the combiners, and we should also leave out the no-operator form ( I've had some ideas over the years, including always requiring three arguments:
But that causes problems with e.g. unquoted variables - what would It could error out, or since there is a valid operator it could assume the other argument is empty. Erroring seems a bit excessive. Or we could require operators first, like you've done here. Then there's the question of what happens if an argument looks like an operator. If we used I don't think that's a huge problem - if you forget to add an operator and the variable looks like one you're gonna have a bad time. A bigger issue is what happens with lists. If we allow
Another possible name: Also there's a backwards-compatibility question: Do we plan to ever remove |
Definitely!
Note I only did that for the path (
Couple of those already: https://packages.debian.org/search?suite=buster&arch=any&mode=exactfilename&searchon=contents&keywords=bin/check (though not a showstopper). I don't think we'd remove our test builtin. |
The more I think about this the more I like it. It's a good thing to have a place to put arbitrary predicates, it doesn't need to be consistent. If we do this, let's dodge the empty expansion problem with strict "verb subject object", never infix:
where if a parameter is missing it always fails. There's nothing to parse so no parsing pitfalls. (Well maybe we can support |
My problem with that is I can never remember which way round the subject and object go. Is it |
My dream shell would imbue |
I was thinking about an optional "than" argument:
but I don't really know that it helps, and mangles the English grammar further. |
I don't think If keeping a polish prefix syntax, it is much better just to stick to it. Here is a table of proposed comparators with examples.
It might be sane to wrap these things. If so, we have a lisp interpreter (Greenspun's tenth rule).
Just some ideas. |
Other comments allude to this, but let me also name drop issue #2037: |
I've been thinking about this some more, and I have a design that looks okay for most things, while being implementable and maintainable. The idea is to avoid the worst And for some tests that's simple:
For these operations that should all be intuitively understandable. I'm leaning towards always testing ALL because otherwise you'd want to have the arguments that passed or failed (which of the given files exists?) The one thing that seems weird with this approach is greater/less, because the logical form of that is:
And that works, but is that something people would reach for? Also note that set foo 1 1 1
set bar 1
is same $foo $bar I don't imagine this to be a problem in practice, and it's only really fixable by having syntax to distinguish between the two lists before expansion. Even if you only allowed two arguments you could have an empty $foo and a bar of two same elements. This just shows that |
Sounds good to check all elements in a list - technically "all" implies truth if no elements are given but we can simply allow that only for Substituting
I think we can still use Prototype (without lifting binary operations to lists yet): set -l is_subcommands empty not same \
file exists directory \
greater-than less-than \
greater-or-equal less-or-equal \
newer-than older-than
complete -c is -n 'is = 1 (count (commandline -opc))' -xa (string join ' ' $is_subcommands)
complete -c is -n '__fish_seen_subcommand_from not !' -xa '(
set -l cmd (commandline -opc) (commandline -ct)
complete -C"is $cmd[3..-1]"
)'
function is -d 'Perform tests on arguments'
set -l argc (count $argv)
set -q argv[1]; or echo 'is: please specify a command' && return 1
set -l subcommands_with_one_argument file exists directory
set -l subcommands_with_two_arguments \
greater-than less-than greater-or-equal less-or-equal \
newer-than older-than
switch $argv[1]
case $subcommands_with_one_argument
if and test $argc -ne 2
echo "is $argv[1]: need exactly one argument"
return 1
end
case $subcommands_with_two_arguments
if test $argc -ne 3
echo "is $argv[1]: need exactly two arguments"
return 1
end
end
switch $argv[1]
# n-ary
case empty
not string length -q $argv[2..-1]
case not !
not is $argv[2..-1]
case same =
set -q argv[3] && test x$argv[2] = x$argv[3]
# unary
case file
test -f $argv[2]
case exists
test -e $argv[2]
case directory
test -d $argv[2]
# binary
case greater-than
test $argv[2] -gt $argv[3]
case greater-or-equal
test $argv[2] -ge $argv[3]
case less-than
test $argv[2] -lt $argv[3]
case less-or-equal
test $argv[2] -le $argv[3]
case older-than newer-than # by mtime
set -l mtimes (stat -c '%Y' $argv[2 3])
set -l op less-than
if is same $argv[1] newer-than
set op greater-than
end
is $op $mtimes
case '*'
echo "is: unknown command: $argv[1]" && return 1
end
end
|
That seems okay to me. The empty string is not the empty list.
To be clear: is ascending 1 2 3 4 5 If you called that "greater-or-equal" it would look like is greater-or-equal 1 2 3 4 5 I would actually assume it's more intuitive to have it the other way around: is less-or-equal 1 2 3 4 5 Because this way you can just slot in a "less-or-equal" comparison between any two numbers in order, so
Those are the ones with the most annoying behavior, yes.
The "definitive" version comparison function is probably rpmvercmp, used by rpm and Archlinux's pacman/ALPM. It's LGPL, so we could use it. (this is also what arch ships as its I'm not saying it's perfect - version comparison is surprisingly hard, with all the gunk people add in there! But it would be a known algorithm that would agree with other, widely used, tools, which is worth a lot. |
Of course I'd mix up
Sounds good, we can add this at some point (or implement it in fish script if someone is feeling adventurous). |
Alternative behavior for E.g. |
Okay, so I've gone a slightly different way and written the documentation first. .. _cmd-is:
is - check conditions on files, numbers and text
================================================
Synopsis
--------
::
is empty STRINGS...
is notempty STRINGS...
is same STRINGS...
is path PATHS...
is file PATHS...
is directory PATHS...
is link PATHS...
is readable PATHS...
is writable PATHS...
is executable PATHS...
is equal NUMBERS...
is greater NUMBERS...
is greater-equal NUMBERS...
is less NUMBERS...
is less-equal NUMBERS...
Description
-----------
``is`` checks if conditions are true. It operates on lists of arguments. The first argument is the command.
A missing or unknown command is an error.
All commands except ``empty`` return false if no argument was passed.
Unlike ``test``, ``is`` does not support logical operators and lacks some negated forms. Use :ref:`combiners <combiners>` and multiple calls to ``is`` instead.
String commands
---------------
- ``empty`` checks if all given strings are empty. This returns true if no string was given.
- ``notempty`` checks if all given strings are notempty.
- ``same`` checks if all the given strings are the same.
To check if any string is not empty, negate the ``is`` instead: ``not is empty "" "" "foo"`` will be true.
To check if a string has one of a number of given values, use :ref:`contains <cmd-contains>`: ``contains -- foo bar foo baz``.
File and directory commands
---------------------------
- ``path`` checks if all given paths exist.
- ``file`` checks if all given paths are regular files.
- ``directory`` checks if all given paths are directories.
- ``link`` checks if all given paths are symbolic links.
- ``readable`` checks if all given paths are readable.
- ``writable`` checks if all given paths are writable.
- ``executable`` checks if all given paths are executable.
Number commands
---------------
``is`` can compare floating point numbers. The radix character is ``.``, not dependent on locale.
- ``equal`` checks if all given numbers are numerically equal - unlike ``same``, this ignores leading zeroes and trailing zeroes after a "." and can handle hexadecimal numbers with ``0x``.
- ``greater`` checks if every number is greater than the next, i.e. the numbers are in descending order.
- ``less`` checks if every number is smaller than the next, i.e. they are in ascending order.
- ``greater-equal``, like ``greater``, but also true if the numbers are equal.
- ``less-equal``, like ``less``, but also true if the numbers are equal.
Examples
--------
From __fish_complete_docutils::
if is same $cmd rst2html5
complete -x -c $cmd -l table-style -a "borderless booktabs align-left align-center align-right colwidths-auto" -d "Specify table style"
From fish_vi_cursor::
# With test - this:
# - needs to test if $KONSOLE_VERSION is non-empty before checking if it's greater
# - needs to quote $KONSOLE_VERSION because `test -n` is *true*
# - still errors out if $KONSOLE_VERSION is non-numeric
not test -n "$KONSOLE_VERSION" -a "$KONSOLE_VERSION" -ge 200400
# With is - no error when the argument is not numeric or missing.
not is greater $KONSOLE_VERSION 200400
From __fish_complete_man::
if test -z "$section" -o "$section" = 1
if is empty $section; or is same $section 1
if test -z "$token" -a "$section" != "[^)]*"
if is empty $token; and not is same $section "[^)]*"
__fish_md5::
# Note: This either needs to be quoted or needs to have been checked beforehand.
if test $argv[1] = -s
if is same $argv[1] -s
if is = $argv[1] -s
Ideas
-----
- Version comparison using ``vercmp``
- ``is true`` - check if a value is "truthy" - number greater than 0, a string like "ON" or "true".
- ``is number`` - check if the value is a number.
- ``--any``, before the command, to return true if any value is true.
- Remove ``notempty``? Add ``notequal``?
- Other names for numeric commands? ``=``?
- Allow the test option naming, possibly as an alternative? "lt"/"le"/"gt"/"ge"?
- ``is prefix``, checking if the first argument is prefix of all the others? (same for suffix etc)
Unimplemented bits:
Some operators for files that nobody really uses much
-----------------------------------------------------
- ``-b FILE`` returns true if ``FILE`` is a block device.
- ``-c FILE`` returns true if ``FILE`` is a character device.
- ``-g FILE`` returns true if ``FILE`` has the set-group-ID bit set.
- ``-G FILE`` returns true if ``FILE`` exists and has the same group ID as the current user.
- ``-k FILE`` returns true if ``FILE`` has the sticky bit set. If the OS does not support the concept it returns false. See https://en.wikipedia.org/wiki/Sticky_bit.
- ``-O FILE`` returns true if ``FILE`` exists and is owned by the current user.
- ``-p FILE`` returns true if ``FILE`` is a named pipe.
- ``-s FILE`` returns true if the size of ``FILE`` is greater than zero.
- ``-S FILE`` returns true if ``FILE`` is a socket.
- ``-t FD`` returns true if the file descriptor ``FD`` is a terminal (TTY).
- ``-u FILE`` returns true if ``FILE`` has the set-user-ID bit set.
Operators to compare and examine numbers
----------------------------------------
- ``NUM1 -ne NUM2`` returns true if ``NUM1`` and ``NUM2`` are not numerically equal.
Operators to combine expressions
--------------------------------
- ``COND1 -a COND2`` returns true if both ``COND1`` and ``COND2`` are true.
- ``COND1 -o COND2`` returns true if either ``COND1`` or ``COND2`` are true.
Expressions can be inverted using the ``!`` operator:
- ``! EXPRESSION`` returns true if ``EXPRESSION`` is false, and false if ``EXPRESSION`` is true.
Expressions can be grouped using parentheses.
- ``( EXPRESSION )`` returns the value of ``EXPRESSION``.
Note that parentheses will usually require escaping with ``\(`` to avoid being interpreted as a command substitution. |
This looks almost done! Regarding other ideas in this thread, it would surely be doable to change We already have something that feels quite natural for both the single-object
I'd say remove For use cases like We could use is not file $foo $bar
not is file $foo && not is file $bar The Python-esque We can also go with verbs that better fit into the list context: is [all] empty $foo $bar
is any empty $foo $bar
is none empty $foo $bar Of course, these could be switches like To keep the natural flow for the more important single-object case, I'd lean towards
I believe that (Pushing that further,
At some point I wasn't sure whether |
Okay, I've implemented my proposal and played around with it a bit. I think it's about the best we could get if we 1. require lists to work, 2. require it to be an ordinary builtin. Unfortunately, I'm not sold that it would actually help anyone. Sure, So, I'm coming around again to that other design where lists fail:
The trick is that if it ever doesn't get exactly three arguments the test fails. No error message, just a falsy return. The exception being The file and such operations are basically like the other design - As above, no logical operations, use This is a good deal less elegant in terms of doing a ton of checks at once - it can't be used to check if a list is sorted. But it is much more intuitive with the infix operators and should still fix the main problems with test - the zero-argument form and the multi-argument confusion. Instead anything weird just causes it to fail, which is most likely what you want anyway. This has some very obscure failure cases - Anything else basically requires special syntax. |
I think that maybe we should split this problem into two problems
For the first problem, I really love the suggestion of introducing a new However, I think that the second problem will still be present in any attempt to replace For example, in the proposal from three weeks ago, if we call The proposal from today of having the comparison command fail unless it receives exactly three arguments helps a bit, but it is just a band-aid that doesn't totally fix the problem of the command doing unexpected things if the number of arguments are not what is expected. If we were allowed to make a backwards incompatible change, my dream proposal would be to replace the On top of that, we could also add an |
It's possible to make the problem much smaller.
In the implementation I've written It doesn't - the binary operators fail if they only get one operand. It only succeeds if you do
I've noted the problem and it is "if the list includes something that perfectly mirrors the other side and operator and includes an =". Which... yeah, sure, quote it if that can happen in your particular case. In most cases it can't, because the shape of the operands is reasonably fixed. Compare |
That is fair. Getting rid of the one-operand mode and and getting rid of combining operators like However, I think the biggest argument in favor of treating For the matter, I actually kind of like the existing |
Lemme head you off right there. The old Replacing test with something incompatible of the same name is a bad idea. Even just removing the one-argument mode (which would fix |
The idea would be to replace the regular test command by a special syntactic form that behaves almost exactly the same. Sort of like I suspect that very few existing Fish programs would behave differently if we replaced the builtin
Backwards compatibility for old scripts would probably be pretty good. The bigger problem would be backwards compatibility for new scripts. Old scripts that use quotes in I think the pros of the change might still outweigh the cons, but I'll admit that I could be wrong about this.
This is actually one of the reasons why I'm attracted to the idea of trying to fix |
From the view of someone who does computer programming as a side-hobby and does not have formal training, I have a few suggestions about the implementation of the One consideration is the possible ambiguity arising from Polish notation, as mentioned earlier in this issue. Leaving aside arrays for the moment, my understanding of the current implementation is as follows. Given Assuming the preceding is true, I wonder if it might be helpful to consider making the verb-object-subject more explicit in regard to the names of the sub-commands to I have a few suggestions along these lines, focusing on readability (and therefore, to some extent, agreement with English language syntax). The proposals are in descending order of degree of change from the current suggested implementation, i.e., Proposal A would be perhaps the most ideal but also the most annoying to implement. Proposal A:Instead of operator overloading for
For the following, I believe there is no current implementation, although many of these have been discussed earlier in this thread.
Regarding the "Ideas" section from @faho's earlier post:
Proposal B:The following changes from Proposal A:
Proposal C:The following changes from Proposal B:
Proposal DThe following changes from Proposal B:
Apologies for the long-winded post. |
Oh, no worries, I see you've given it some thought!
It isn't. It's exactly the other way around. The idea is it returns the same thing as if the operator was between the arguments - is greater a b c
# is true if a > b > c I had written a longer answer and accidentally lost it, so let me summarize my criticisms with your proposals:
Yeah, that's... just your opinion. "More readable" is one of those empty phrases that's super easy to throw around.
We really really really want to move people away from Which means finding a carrot for the replacement, something that is awesome about it that makes you want to use it, and I'm not sure we've got it yet. |
I'm sorry, I should have double-checked; I conflated Polish notation with verb-object-subject, but I can see above that @ridiculousfish said "verb subject object". In part, I confused myself because I was assuming consistency with
I'm sorry to hear that! I know that can be incredibly frustrating.
I agree that many of my suggestions are centered around renaming, although my intent was to underscore the importance of word-choice as it seems that you are moving closer toward implementation. I did not mean to imply we should cater to my personal preferences! Rather, I was hoping we could consider word-choice and start to develop a consensus based on the goals of minimal ambiguity and maximum consistency. My hope is to avoid replacing a large foot-gun with a small foot-pistol, so-to-speak.
Absolutely, I agree. This suggestion stems from my mistaken assumption that verb-object-subject order was being used, so the
Upon reflection, you've convinced me about not "dipping into the global namespace twice for common words." I think it may still be worth thinking about if are ascending $x $y $z easier to understand, less ambiguous, etc., than: if is greater $x $y $z
Okay, I understand the reasoning behind
Sure, but when I first started learning fish, I thought of
Sure, that's completely understandable.
I didn't realize it would require a sha256 implementation in fish; agree that it would not be worth it.
Was this a The Big Lebowski reference? Anyway, I'm not sure that I agree with it being an entirely "empty" phrase, but perhaps "more readable" was a bad choice of words. I suppose in part it depends on your intended audience, but I can see why from your point of view
I hear you.
Seems like you're getting there! |
test
sucks (#1337, #5186, #5947, #6114). Plus I always have to look up the manpage because I can never remember what the options do. Its one saving grace is its compliance with POSIX, so rather than enhancing it beyond that, can we do better? Bash/zsh has[[
, which is mostly backward-compatible, but I think we should take a different approach.I propose a new builtin, which I will call
is
for now.Some examples:
Replaces #6065. Should include #3589.
Absolutely must not include combining operators.
Should not include operations available elsewhere (eg
isatty
,string
,contains
); I'd be tempted to elide string operations entirely in favour ofstring match --entire
andstring length
, though perhaps some shorthand could be added.One problem then is that
is
is then numeric comparisons and file operations, which are not at all related; perhaps it could be broken out intoisnum
andispath
?Name open to debate (though
is
doesn't collide with any binaries in Debian, at least).The text was updated successfully, but these errors were encountered: