Using logical OR (||) in C-Shell - shell

I have a C-Shell code where I use the following command:
if($#arr == 0 || $arr[1] == "test1") then
It outputs an error message saying "tcsh: arr: Subscript out of range." obviously because the first condition is true. Is it possible to force it to ignore the second condition if the first condition is true?

csh parses the entire line and substitutes variables before evaluating the expression. so in this case, you'd need a nested if because the or condition does not implement short-circuit evaluation with respect to variables.

Related

Bash: usage of `true`

In many scripts I've inherited from a former employee I keep seeing this pattern:
if (true $SOME_VAR)&>/dev/null; then
...
fi
or this one
(true $SOME_VAR)&>/dev/null || SOME_VAR="..."
The man page for true says it always returns true, hence I keep wondering, what is the point of these checks? In the first case the then part is always executed, in the second case the right hand part is never executed.
If set -u (a.k.a. set -o nounset) is in effect, true $SOME_VAR will fail when $SOME_VAR is not defined. This is therefore a way to test whether the variable is defined.
To complement jwodder's helpful answer and Fred's helpful answer:
In Bash v4.2+
, the less obscure and more efficient -v operator can be used to test if a variable is defined[1] (note that no $ must be used):
[[ -v SOME_VAR ]]
In older Bash versions and in POSIX-compliant scripts, use Fred's parameter-expansion-based approach, which is also more efficient than the (true ...) approach.
If the intent is to simply provide a default value, as in the (true $SOME_VAR)&>/dev/null || SOME_VAR="..." idiom, use the (POSIX-compliant) technique suggested by kojiro, also based on a parameter expansion:
SOME_VAR=${SOME_VAR-...} # keep $SOME_VAR value or default to '...'
Toby Speight suggests another POSIX-compliant variant, ${SOME_VAR=...}, which directly updates the variable with the default value, if it is undefined; however, it has the side effect of expanding to the (resulting) value - which may or may not be desired. A concise, but also slightly obscure way to suppress the expansion is to pass the expansion to the colon (null) utility (:), which expands, but otherwise ignores its arguments (compared to using true for the same purpose, it is perhaps slightly less confusing):
: ${SOME_VAR=...} # set $SOMEVAR to '...' only if not defined
Note that all parameter expansions shown/mentioned above have a variant that places : before the operator, which then acts not only when the variable is undefined, but also when it is defined but empty (contains the null string):
${SOME_VAR:+...}, ${SOME_VAR:-...}, ${SOME_VAR:=...}
Arguably, this variant behavior is the generally more robust technique, especially given that when set -u (set -o nunset) is not turned on, undefined variables expand to the null (empty) string.
To add to jwodder's explanation:
The use of (...) around true $SOME_VAR to create a subshell is crucial for this somewhat obscure test for variable existence to work as intended.
Without a subshell, the entire script would abort.
The need for a subshell makes the technique not just obscure, but also inefficient (although that won't really be noticeable with occasional use).
Additionally, if set -u (set -o nounset) happens not to be in effect, the technique treats all variables as defined.
With the subshell, only the subshell aborts, which is reflected in its exit code to the current shell: 1, if the subshell aborted (the variable doesn't exist), 0 otherwise.
Therefore, the (true ...) command only evaluates to (conceptually) true if the variable exists.
&>/dev/null suppresses the error message from the subshell that is emitted if the variable doesn't exist.
As an aside: true never produces no output, so it is sufficient to use (true $SOME_VAR)2>/dev/null (suppress stderr only) - this change makes the technique POSIX-compliant (though still not advisable).
It isn't just set -u (set -o nounset) statements inside a script that turn on aborting in case of access to an undefined variable - invoking bash explicitly with command-line option -u has the same effect.
[1] Since Bash v4.3, you can also test whether an array variable has an element with the specified index; e.g.:
a=( one two ); [[ -v a[0] ]] succeeds, because an array element with index 0 exists; works analogously with associative arrays.
The following is probably equivalent, and more straightforward :
if [ "${SOME_VAR+x}" ] then
...
fi
Or, in the assignment case :
[ "${SOME_VAR+x}" ] || SOME_VAR="..."
The + expansion operator expands to a null string if the variable is unset, and to x if it is assigned (assigned a null string still means assigned). In this case, you could replace x by whatever you want (except a null string).
There is also a ${SOME_VAR:+x} variant. The difference is with null strings : :+ expands to a null string if the variable is assigned a null string (while + expands to x if the value is assigned, even if it is a null string).
While not strictly the same,
if [ x"$SOME_VAR" = x ]; then
...
fi
tends to do what you want; that is the if is true if $SOME_VAR is undefined or (difference:) defined to be the zero-length string.
This code does not work if SOME_VAR is unset and -u is set. I believe the following bashism works though: "${SOME_VAR-}" = "".

Why AND && logic is used in chained commands in most shells, not OR || logic

I am curious about the logic behind && in continuous commands execution in shell.
Lets see an example: command_a && command_b && command_c
In shell, this means that once a command fails, the consequent commands will not execute and the chain should stop.
If we replace each command by its exit code, the chain becomes, for example this expression 0 && 1 && 0. (command_a succeed, command_b failed, command_c succeed)
If we try evaluating this expression, the evaluation should stop right after the first command, 0 value.
If && logic is replaced by ||, the expression would be more fit to the meaning of original chained command.
0 || 1 || 0.
Expression's evaluation stops after command_b execution
There's a difference between the semantics of a successful command and the representation of success via the numeric value of the exit code. If you consider the abstract "is a command successful", && makes more sense, since the "success" abstract boolean is true. That's why && is used. You need A to run, AND you need B to run.
But, due to the fact that there's usually only one status for success, but many different types of errors, the value 0 has been defined long ago to be used for exit status to indicate success.
So, the exit status of a command can simply not replace the command itself in such an expression. The semantics and the representation are just different.
Check out this post.
"The right side of && will only be evaluated if the exit status of the left side is zero. || is the opposite: it will evaluate the right side only if the left side exit status is nonzero..."
$ false && echo howdy!
$ true && echo howdy!
howdy!
$ true || echo howdy!
$ false || echo howdy!
howdy!
I have seen || used in some shell script too. I think it depends on the occasions. You may use command-a && command-b when you want command-b to be executed only after command-a success.
Likewise, you may use command-a || command-b to deal with the situation when command-a fails.
Update: After reading your question three times I now understand what puzzles you: That 0 represents success, and/or (sic) that logical operators treat it as true. Yes, that can be confusing, coming from C. The other Peter's answer explains that well. I let my original answer stand anyway because it is not "wrong".
It is what logic dictates if commands are impossible to perform or don't make sense unless their predecessors succeeded. That happens quite often: "Retrieve a HTML document from the web, verify the time stamp, parse a value out of it, and write that value as an updated data somewhere." Each step depends on the success of all preceding steps.
If, instead, you have something "retrieve a document from the web, or from the cache, or from disk, or take a default here document, in this order of preference", then the appropriate way to write that is indeed with logical ORs. This happens but is less common.
ORing is a common idiom though for error handling, because subsequent commands are exactly performed if the pervious failed. Consider Perl's idiomatic cmd() || die();. (You have more lives. Phew.)

What does the =~ operator do? [duplicate]

This question already has an answer here:
Meaning of "=~" operator in shell script [duplicate]
(1 answer)
Closed 8 years ago.
I have come across this operator =~ and couldn't figure it out on what it does. Could someone with bash knowledge kindly help me out?
man bash
/=~
An additional binary operator, =~, is available, with the same precedence as == and !=. When it is used, the string to the right of the operator is considered an
extended regular expression and matched accordingly (as in regex(3)). The return value is 0 if the string matches the pattern, and 1 otherwise. If the regular
expression is syntactically incorrect, the conditional expression's return value is 2. If the shell option nocasematch is enabled, the match is performed without
regard to the case of alphabetic characters. Substrings matched by parenthesized subexpressions within the regular expression are saved in the array variable
BASH_REMATCH. The element of BASH_REMATCH with index 0 is the portion of the string matching the entire regular expression. The element of BASH_REMATCH with index n
is the portion of the string matching the nth parenthesized subexpression.
In ~ is saved your home directory (for example: /home/username) and = is assignment operator.
If you run this code in bash:
x=~ # variable x will hold string value /home/your_username
echo $x # print variable x via echo command
it will print something like: /home/your_username

multiple replacements on a single variable

For the following variable:
var="/path/to/my/document-001_extra.txt"
i need only the parts between the / [slash] and the _ [underscore].
Also, the - [dash] needs to be stripped.
In other words: document 001
This is what I have so far:
var="${var##*/}"
var="${var%_*}"
var="${var/-/ }"
which works fine, but I'm looking for a more compact substitution pattern that would spare me the triple var=...
Use of sed, awk, cut, etc. would perhaps make more sense for this, but I'm looking for a pure bash solution.
Needs to work under GNU bash, version 3.2.51(1)-release
After editing your question to talk about patterns instead of regular expressions, I'll now show you how to actually use regular expressions in bash :)
[[ $var =~ ^.*/(.*)-(.*)_ ]] && var="${BASH_REMATCH[#]:1:2}"
Parameter expansions like you were using previously unfortunately cannot be nested in bash (unless you use ill-advised eval hacks, and even then it will be less clear than the line above).
The =~ operator performs a match between the string on the left and the regular expression on the right. Parentheses in the regular expression define match groups. If a match is successful, the exit status of [[ ... ]] is zero, and so the code following the && is executed. (Reminder: don't confuse the "0=success, non-zero=failure" convention of process exit statuses with the common Boolean convention of "0=false, 1=true".)
BASH_REMATCH is an array parameter that bash sets following a successful regular-expression match. The first element of the array contains the full text matched by the regular expression; each of the following elements contains the contents of the corresponding capture group.
The ${foo[#]:x:y} parameter expansion produces y elements of the array, starting with index x. In this case, it's just a short way of writing ${BASH_REMATCH[1]} ${BASH_REMATCH[2]}. (Also, while var=${BASH_REMATCH[*]:1:2} would have worked as well, I tend to use # anyway to reinforce the fact that you almost always want to use # instead of * in other contexts.)
Both of the following should work correctly. Though the second is sensitive to misplaced characters (if you have a / or - after the last _ it will fail).
var=$(IFS=_ read s _ <<<"$var"; IFS=-; echo ${s##*/})
var=$(IFS=/-_; a=($var); echo "${a[#]:${#a[#]} - 3:2}")

Boolean in Shell Scripting

I have a problem with boolean for while loop. As such, I switch to for loop instead.
But still, I cannot change the value of a boolean after the condition is met.
doFirst= true
for (( j=1; j<=7; j++))
do
letter="A"
seatChoses=$letter$j
flagRand=$(echo $flightSeatBooked | awk -v flseatRand=$flightSeatBooked -v orseatRand=$seatChoses '{print match(flseatRand, orseatRand)}')
if $doFirst ; then
**$doFirst= false** // Here is the error!
if [ $flagRand -eq 0 ]; then
echo "System generated a slot, "$seatChoses" for you. [Y or N]"
fi
fi
done
There is no such thing as a boolean value in a shell script (that is, something you can store in a variable, and treat as a boolean). true and false are commands; true exits with value 0, and false exits with a nonzero value. An if statement in bash taks a command; if that command returns 0, then the then clause is executed, otherwise the else clause is.
doFirst= true
This line doesn't do what you expect at all. In a shell script, you cannot have any spaces after the equals sign. The space means you're done with the assignment, and now writing a command. This is equivalent to:
doFirst="" true
Furthermore, if you have an assignment before a command (like this), that doesn't actually perform the assignment in the shell. That sets that environment variable in the environment for that command alone; the assignment has no effect on anything outside of that command.
if $doFirst ; then
This expands the $doFirst variable, and tries to interpret the result as a command. Oddly, if $doFirst is undefined (which it is, as I explain above), this takes the then branch. At that point, you make your first mistake again, trying to set a variable to be false, and again, nothing happens; $doFirst is left undefined. You make the further mistake of trying to assign $doFirst; you use $ to get the value of a variable, when setting, you use the bare name.
My recommendation would be to not try to use booleans in Bash; just use strings instead, and check the value of the string. Note that I remove the space, so now I'm setting it to that exact string; and there is no command, so this sets the variable within the shell, not in the environment for a single command:
doFirst=true
# ...
if [ $doFirst = true ]; then
doFirst=false
# ...
Are you actually putting a space between the = and the "true"/"false" or is that a formatting error? That's one of your problems.
Another, as mentioned by Anders Lindahl in the comment section, is that when you set a variable in shell scripting, you cannot use the $ in the front. You must say
doFirst=false
Again, note that there are no spaces around the equals sign.

Resources