I have just started writing shell scripts. In if constructs, is the if considered to be a command or a keyword?
If it is a command, its path should be listed when searched through the which command. In reality, which does not find anything.
If if is not a command then ideally it does not need to be separated by a semicolon when then is written in the same line.
Can any of you please explain whether if is a command or a statement?
In bash if is a compound command (see "Shell Grammar" in the linked page.)
if is not a command it is the shell's construct.
Semicolon ; after condition is the shell syntax to terminate the condition and is required only when then keyword is on the same line. ; is not mandatory when then keyword appears on the next line.
So this will also work without semicolon and print date:
if ((RANDOM>0))
then
date
fi
if is a keyword. The semi-colon terminates the command that is between if and then.
Related
When posting this question originally, I totally misworded it, obtaining another, reasonable but different question, which was correctly answered here.
The following is the correct version of the question I originally wanted to ask.
In one of my Bash scripts, there's a point where I have a variable SCRIPT which contains the /path/to/an/exe which, when executed, outputs a line to be executed.
What my script ultimately needs to do, is executing that line to be executed. Therefore the last line of the script is
$($SCRIPT)
so that $SCRIPT is expanded to /path/to/an/exe, and $(/path/to/an/exe) executes the executable and gives back the line to be executed, which is then executed.
However, running shellcheck on the script generates this error:
In setscreens.sh line 7:
$($SCRIPT)
^--------^ SC2091: Remove surrounding $() to avoid executing output.
For more information:
https://www.shellcheck.net/wiki/SC2091 -- Remove surrounding $() to avoid e...
Is there a way I can rewrite that $($SCRIPT) in a more appropriate way? eval does not seem to be of much help here.
If the script outputs a shell command line to execute, the correct way to do that is:
eval "$("$SCRIPT")"
$($SCRIPT) would only happen to work if the command can be completely evaluated using nothing but word splitting and pathname expansion, which is generally a rare situation. If the program instead outputs e.g. grep "Hello World" or cmd > file.txt then you will need eval or equivalent.
You can make it simple by setting the command to be executed as a positional argument in your shell and execute it from the command line
set -- "$SCRIPT"
and now run the result that is obtained by expansion of SCRIPT, by doing below on command-line.
"$#"
This works in case your output from SCRIPT contains multiple words e.g. custom flags that needs to be run. Since this is run in your current interactive shell, ensure the command to be run is not vulnerable to code injection. You could take one step of caution and run your command within a sub-shell, to not let your parent environment be affected by doing ( "$#" ; )
Or use shellcheck disable=SCnnnn to disable the warning and take the occasion to comment on the explicit intention, rather than evade the detection by cloaking behind an intermediate variable or arguments array.
#!/usr/bin/env bash
# shellcheck disable=SC2091 # Intentional execution of the output
"$("$SCRIPT")"
By disabling shellcheck with a comment, it clarifies the intent and tells the questionable code is not an error, but an informed implementation design choice.
you can do it in 2 steps
command_from_SCRIPT=$($SCRIPT)
$command_from_SCRIPT
and it's clean in shellcheck
I saw an expression a=($(cat)) which I am not able to understand from it's working mechanism perspective.
Functionally it takes input from the standard input and assigns it to variable a (which forms an array).
My understanding is , when shell executes the inner parenthesese it executes the cat command which brings the standard input, and when you type a few lines on the standard input and press CTRL+D it returns the lines to the outer parenthesese which then assign the lines to an array a.
My question is why this expression gives error when I remove the $ and write it as a=((cat)).
It is because $(..) is a command substitution syntax to run commands on. The cat in your example run in a sub-shell under this construct. Without it the command cat and ( are interpreted literally which the shell does not like
From the bash(1) - Linux man page
Command Substitution
Command substitution allows the output of a command to replace the command name. There are two forms:
$(command) (or) command
Bash performs the expansion by executing command and replacing the command substitution with the standard output of the command, with any trailing newlines deleted.
The arithmetic operator in bash is $((..)) which is not the syntax you are using in your example
This question already has answers here:
Getting ‘Command not found’ errors when there is no space after the opening square bracket [duplicate]
(2 answers)
How do I compare two string variables in an 'if' statement in Bash? [duplicate]
(12 answers)
Closed 5 years ago.
I am new with bash and I try to use the if statement, so I tried this short piece of code:
#!/bin/bash
if ["lol" = "lol"];
then
echo "lol"
fi
And I get the following error:
./script.sh: line 2: [lol: command not found
I tried other combinations, like:
#!/bin/bash
if ["lol" == "lol"];
then
echo "lol"
fi
but I still get errors, so what would be the correct formulation ?
Thank you
Although the problem was solved in a comment above, let me give you a bit more information.
For Bash, [ is a command, and there is nothing special about that command. Bash parses the following arguments as data and operators. Since these are normal arguments to a normal command, they need to be separated by spaces.
If you express a test as ["lol" = "lol"], Bash reads this as it would read any command, by performing word splitting and expansions. This gets rid of quotes, and what is left after that is [lol = lol]. Of course, [lol is not a valid command, so you get the error message you saw.
You can test this with another command. For instance, type l"s" at the command line, and you will see Bash execute ls.
You would not write ls/ (without the space), so for the same reason you cannot write [a=b] either.
Please note ] simply is a closing argument that command [ expects. It has no purpose in itself, and requiring it is simply a design choice. The test command actually is entirely equivalent to [, aside from not needing the closing bracket.
One last word... [ is a builtin in Bash (meaning a command that is part of Bash itself and executes without launching a separate process, not a separate program), but on many systems you will also find an executable named [. Try which [ at the command line on your system, it will probably be there.
I'm trying to create a Here Document which is a shell script that includes the cat command. Of course, it fails when encountering the 2nd cat. I'm performing a lot of substitutions as well, so can't use the "DOC" escape trick.
myfile="/tmp/myipaddr"
cat >/usr/bin/setIPaddress <<_DOC_
...
OUT=`cat $myfile`
...
_DOC_
I supposed I could echo into a file, but that seems kludgy and I have a lot of quotes and backticks I'd need to escape?!? Any other thoughts?
Suppose the file contains
hello world
As written, the script you generate will contain the line
OUT=hello world
because the command substitution is performed immediately.
At the very least, you need to quote the line in the here document as
OUT="`cat $myfile`"
I suspect what you want is to include the literal command substitution in the resulting shell script. To do that, you would want to quote the backticks to prevent them from being evaluated immediately. Better still, use the recommended form of command substitution, $(...), and quote the dollar sign.
cat >/usr/bin/setIPaddress <<_DOC_
...
OUT=\$(cat $myfile)
...
_DOC_
/usr/bin/setIPaddress will then include the line
OUT=$(cat /tmp/myipaddr)
When I'm interacting with the shell or writing a bash script I can do:
somecmd "some
arg"
Say now that I want to do the same in vim command-line mode:
:!somecmd "some<Enter>arg"
obviously won't work: as soon as I press <Enter> the command is executed. But neither the following do:
:!somecmd "some<C-V><Enter>arg"
:!somecmd "some<C-V>x0Aarg"
The first one inserts a carriage return instead of a line feed, which is right. The second one will break the command in two, trying to execute somecmd "some<C-V> first and then arg", both of which fail miserably.
I guess I could work around this using some echo -e command substitution, or embedding $'\n', but is it possible to type it directly in vim's command-line? I don't fully understand why the "some<C-V>x0Aarg" form doesn't work while $'some\narg' does. Is vim parsing the string previously to shell evaluation?
Well, I've found the answer myself, but I'm leaving here for further reference anyway. The documentation of :! states:
A newline character ends {cmd}, what follows is interpreted as a following ":" command. However, if there is a backslash before the newline it is removed and {cmd} continues. It doesn't matter how many backslashes are before the newline, only one is removed.
So you (I) should type "some\<C-V>x0Aarg" instead of "some<C-V>x0Aarg".
Plus, I could have done it using the system() function instead of the :! command:
:call system("somecmd 'some<C-V>x0Aarg'")