fishshell checking equvalancy. (like `==` does in most languages) - fish

All I want to do is perform a simple equivalency check like you would in other languages with the == operator. It would look something like
if $var == 0
echo Hello world!
end
I'm a really surprised that the closest thing in the docs I could find is contains which allows for this silly makeshift solution
if contains $var 0
echo Hello world!
end

Use the test builtin, like
if test "$var" = 0
# do stuff
end
test is also available as [, in which case it expects the last argument to be ], so you can write
if [ "$var" = 0 ]
# do stuff
end
I'm quoting the variable here because tests argument parsing (which was taken straight from POSIX) doesn't work well with fish's lists, so if $var doesn't have exactly one element it will break with surprising errors.

Related

/bin/sh Adding an element to the arguments array from within the script

Ultimate Goal
Essentially I have a while loop going through each command-line argument passed to the script, but want to set default behaviour if no arguments are passed.
Current solution idea
My plan for this is to test if there are any arguments prior to this while loop and if there aren't any, simply add the default arguments (flags in this case) to the argument array and let the script execute from there as normal.
Problem I'm having
Where I'm tripping up a little here is figuring out the syntax to add these default arguments to the arguments array itself (I can get values from it no problem).
This is what I have so far:
if test $# -eq 0
then
# ADD --default TO ARGUMENTS ARRAY HERE
fi
while test $# -gt 0
do
case "$1" in
--opt1) DO STUFF
;;
--opt2) DO MORE / OTHER STUFF
;;
--default) DO DEFAULT STUFF
;;
esac
shift
done
Following and altering examples using user-defined arrays I have tried:
if test $# -eq 0
then
$+=('--default')
fi
But just get the syntax error
syntax error near unexpected token `'--default''
sh does not have arrays other than the built-in command-line argument list, and thus no += operator for arrays.
You also seem to be missing a character; what you wrote evaluates to assigning (default) to the value of the variable $+ but this variable is not valid (though because there are no arrays, the parentheses cause an error already).
The shell allows you to use set to manipulate the built-in argument list.
if test $# -eq 0
then
set -- --default
fi
The condition requires the array to be empty, so we simply populate it with the single element you provided; in the general case, you could add stuff to the end with
set -- "$#" --default
or correspondingly to the beginning by putting something before "$#" (which of course contains the previous value of the argument list).

Why are ##, #!, #, etc. not interpolated in strings?

First, please note that I ask this question out of curiosity, and I'm aware that using variable names like ## is probably not a good idea.
When using doubles quotes (or qq operator), scalars and arrays are interpolated :
$v = 5;
say "$v"; # prints: 5
$# = 6;
say "$#"; # prints: 6
#a = (1,2);
say "#a"; # prints: 1 2
Yet, with array names of the form #+special char like ##, #!, #,, #%, #; etc, the array isn't interpolated :
#; = (1,2);
say "#;"; # prints nothing
say #; ; # prints: 1 2
So here is my question : does anyone knows why such arrays aren't interpolated? Is it documented anywhere?
I couldn't find any information or documentation about that. There are too many articles/posts on google (or SO) about the basics of interpolation, so maybe the answer was just hidden in one of them, or at the 10th page of results..
If you wonder why I could need variable names like those :
The -n (and -p for that matter) flag adds a semicolon ; at the end of the code (I'm not sure it works on every version of perl though). So I can make this program perl -nE 'push#a,1;say"#a"}{say#a' shorter by doing instead perl -nE 'push#;,1;say"#;"}{say#', because that last ; convert say# to say#;. Well, actually I can't do that because #; isn't interpolated in double quotes. It won't be useful every day of course, but in some golfing challenges, why not!
It can be useful to obfuscate some code. (whether obfuscation is useful or not is another debate!)
Unfortunately I can't tell you why, but this restriction comes from code in toke.c that goes back to perl 5.000 (1994!). My best guess is that it's because Perl doesn't use any built-in array punctuation variables (except for #- and #+, added in 5.6 (2000)).
The code in S_scan_const only interprets # as the start of an array if the following character is
a word character (e.g. #x, #_, #1), or
a : (e.g. #::foo), or
a ' (e.g. #'foo (this is the old syntax for ::)), or
a { (e.g. #{foo}), or
a $ (e.g. #$foo), or
a + or - (the arrays #+ and #-), but not in regexes.
As you can see, the only punctuation arrays that are supported are #- and #+, and even then not inside a regex. Initially no punctuation arrays were supported; #- and #+ were special-cased in 2000. (The exception in regex patterns was added to make /[\c#-\c_]/ work; it used to interpolate #- first.)
There is a workaround: Because #{ is treated as the start of an array variable, the syntax "#{;}" works (but that doesn't help your golf code because it makes the code longer).
Perl's documentation says that the result is "not strictly predictable".
The following, from perldoc perlop (Perl 5.22.1), refers to interpolation of scalars. I presume it applies equally to arrays.
Note also that the interpolation code needs to make a decision on
where the interpolated scalar ends. For instance, whether
"a $x -> {c}" really means:
"a " . $x . " -> {c}";
or:
"a " . $x -> {c};
Most of the time, the longest possible text that does not include
spaces between components and which contains matching braces or
brackets. because the outcome may be determined by voting based on
heuristic estimators, the result is not strictly predictable.
Fortunately, it's usually correct for ambiguous cases.
Some things are just because "Larry coded it that way". Or as I used to say in class, "It works the way you think, provided you think like Larry thinks", sometimes adding "and it's my job to teach you how Larry thinks."

Fish Shell: Conditional execution based on $status?

In the fish shell the syntax for conditionals is hard to find. Does anyone have a link to an explanation of how to write an if, with ands and ors?
In particular I would like to write
if not $status
do a command
end
To do a command when the previous command returned a non-success. How do I do that?
See http://fishshell.com/docs/current/commands.html#if and http://fishshell.com/docs/current/tutorial.html#tut_conditionals.
Fish's if structure looks like this:
if COMMAND
# do something if it succeeded
else
# do something if it failed ($status != 0)
end
Then there are also the not, and and or commands, that you can use like
if not COMMAND1; or COMMAND2
# and so on
If you really want to test a variable (e.g. $status), you need to use test as the command, like
if test $status -eq 0
Do keep in mind that $status changes after every command, so if you need to use the status of an earlier command (common in prompts) you need to do what Joachim Pileborg said, save it to another variable.
Also, test has some issues with quotes (because it's one of the few parts of fish to adhere to POSIX) - if in test $foo -eq 0 $foo is undefined, test will error out, and if it is undefined in test -n $foo, the test will be true (because POSIX demands that test with one argument be true).
As a sidenote, in fish versions before 2.3.0, you need to add begin and end around a condition with and or or because it was interpreted weirdly.
So you'd have to do
if begin COMMAND; or COMMAND2; end
# do something for status = 0
The shortest short form would be
the_previous_command; or do_a_command
# ..................^^^^^
Assuming you get your $status from "the_previous_command"
I use the status variable to display it on the prompt if it's non-zero. For that I use the following function:
function __pileon_status_prompt
set -l __status $status
if test $__status != 0
printf '[%s%s%s]' (set_color red) $__status (set_color normal)
end
end
As you can see I set a local variable to the value of $status and the check that variable in the condition.

Can someone explain what these special variables mean?

I'm re-learning scripting and found an example the book doesn't explain very well.
e.g.:
if [ ! $# -eq 0 ]
This is testing to determine if '$#' does not equal zero, yes?
But what then is the value of '$#'?
Are there others?
'##', '#?' ?
Thank you
$# is the number of the arguments you passed to your script.
For example, your have script called a.sh,
#!/bin/bash
echo $#
And you run it like
/bin/bash a.sh 1 2 3
you will get 3.
There are others like $#.

To fix a bug in Less' command by noting no. of parameters

This question is based on this thread.
The code
function man()
{
man "$1" > /tmp/manual; less /tmp/manual
}
Problem: if I use even one option, the command does not know where is the wanted-manual
For instance,
man -k find
gives me an error, since the reference is wrong. The command reads -k as the manual.
My attempt to solve the problem in pseudo-code
if no parameters
run the initial code
if one parameter
run: man "$2"
...
In other words, we need to add an option-check to the beginning such that
Pseudo-code
man $optional-option(s) "$n" > /tmp/manual; less /tmp/manual
where $n
n=1 if zero options
n=2 if 1 option
n=3 if 2 options
....
How can you make such an "option-check" that you can alter the value of $n?
Developed Problem: to make two if loops for the situations from n=1 to n=2
How about passing all the arguments
function man()
{
man $# > /tmp/manual; less /tmp/manual
}
What is the bug in less which you mention in the title?
First, you can pass all of your function's arguments to man by using $* or $#. You can read man sh for the precise details on the difference between the two; short story is to almost always use "$#" with double quotes.
Second, the temporary file is unnecessary. You could make this a little cleaner by piping the output of man directly to less:
function man() {
man "$#" | less
}
By the way, if you're just trying to use a different pager (man uses more and you want the fancier less) there's a commonly recognized PAGER environment variable that you can set to override the default pager. You could add this to your ~/.bashrc for instance to tell all programs to use less when displaying multiple screens of output:
export PAGER=less
To answer your precise question, you can check the number of arguments with $#:
if [ $# -eq 0 ]; then
: # No arguments
elif [ $# -eq 1 ]; then
: # One argument
# etc.
You might also find the shift command helpful. It renames $2 to $1, $3 to $2, and so on. It is often used in a loop to process command-line arguments one by one:
while [ $# -gt 1 ]; do
echo "Next argument is: $1"
shift
done
echo "Last argument is: $1"