Why is the 'if [ $1="1" ]' branch always selected even if $1 is not 1?
Solution 1
Using spaces fixes your problem.
if [ "$1" = 1 ];
then
shift
mv "$@" ~/lab/Sun
elif [ "$1" = 2 ];
then
shift
mv "$@" ~/lab/Moon
elif [ "$1" = 3 ];
then
shift
mv "$@" ~/lab/Earth
fi
Though this is neater:
#!/bin/bash
action=$1
shift
files=("$@")
case $action in
1) mv -- "${files[@]}" ~/lab/Sun ;;
2) mv -- "${files[@]}" ~/lab/Moon ;;
3) mv -- "${files[@]}" ~/lab/Earth ;;
esac
Solution 2
First obvious thing is you should provide spaces between the arguments of [
, test
or [[
:
if [ "$1" = 1 ];
When in Bash, using [[ ]]
is recommended as it doesn't do things unnecessary for conditional expression like word splitting and pathname expansion. Quoting around double-quotes is also not needed. A more readable operator ==
can also be used.
if [[ $1 == 1 ]];
Added note: If second operand also contains variables, quoting is necessary as it may be subject to pattern matching if it contains recognizable characters like *
, ?
, []
, etc.. If extended globbing or pattern matching is enabled with shopt -s extglob
, other forms like @()
, !()
, etc. will also be recognized as patterns. See Pattern Matching.
With operators like <
and >
it may still be necessary as I had once encountered a bug where not quoting the second argument caused different results.
As for the first operand, nothing applies.
Consider this simpler variation as well:
case "$1" in
1)
mv -- "${@:2}" ~/lab/Sun
;;
2)
mv -- "${@:2}" ~/lab/Moon
;;
3)
mv -- "${@:2}" ~/lab/Earth
;;
esac
Or condensed:
case "$1" in
1) mv -- "${@:2}" ~/lab/Sun ;;
2) mv -- "${@:2}" ~/lab/Moon ;;
3) mv -- "${@:2}" ~/lab/Earth ;;
esac
"${@:2}"
is a form of substring expansion or array member expansion where 2
is the offset. This makes expansion start at the second value. With this we may not need to use shift
.
The added --
prevents mv
from recognizing filenames starting with dash (-
) as invalid options.
Solution 3
To answer the question of why this is happening, this behavior of [
aka test
is documented in POSIX:
In the following list, $1, $2, $3, and $4 represent the arguments presented to test:
[...]
1 argument:
Exit true (0) if $1 is not null; otherwise, exit false.
You're passing it 1 argument, 2=1
, which is not null, and therefore test
exits with success.
As other posts (and shellcheck) point out, if you wanted to compare for equality, you would instead have to pass the 3 arguments 2
, =
and 1
.
Solution 4
I just want to recommend a portable yet also neater alternative. Bash is not universal (and if you don't need universal, why are you writing a shell script?)
#! /bin/sh
action="$1"
shift
case "$action" in
1) dest=Sun ;;
2) dest=Moon ;;
3) dest=Earth ;;
*) echo "Unrecognized action code '$action' (must be 1, 2, or 3)" >&2; exit 1 ;;
esac
mv -- "$@" ~/lab/"$dest"
(Note to pedants: yes, I know the quotes around $action
on the case "$action" in
line are unnecessary, but I feel it is best to put them there anyway, so that future readers don't have to remember that.)
Related videos on Youtube
Zen
Updated on September 18, 2022Comments
-
Zen almost 2 years
I have a shell script named 'teleport.sh' like this:
if [ $1="1" ]; then shift mv "$@" ~/lab/Sun elif [ $1="2" ]; then shift mv "$@" ~/lab/Moon elif [ $1="3" ]; then shift mv "$@" ~/lab/Earth fi
When I execute:
sh teleport.sh 2 testfile
This
testfile
is moved to the~/lab/Sun
directory, which confuses me a lot as I didn't pass 1 or '1' to that script.What's wrong here?
-
Zen almost 10 years@nyuszika7h, shouldn't double quote means "$var" and "$cmd"? what's the benefit of the round bracket you've mentioned above?
-
nyuszika7h almost 10 years
$(cmd)
is command substitution, (mostly) the same as`cmd`
. See mywiki.wooledge.org/CommandSubstitution and mywiki.wooledge.org/BashFAQ/082
-
-
Арсений Черенков almost 10 yearsshouldn't you break in each case ?
-
Mat almost 10 years@Archemar: no, there's no fall-through (contrary to a lot of other languages).
-
Stéphane Chazelas almost 10 years@Mat, there's fall-through if you use
;&
instead of;;
(ksh, bash, zsh only). But thenbreak
still does not prevent the fall-through,break
is only to break out of loops. -
Stéphane Chazelas almost 10 yearsThat's not arguments to
if
but to the[
command. -
konsolebox almost 10 years@StéphaneChazelas Yeah it's a quick-answer mistake.
-
echristopherson almost 10 yearsYes, the spaces are necessary, but I'm wondering why the original
$1="1"
syntax "works" at all (producing a true result). How does the[
/test
builtin actually interpret that expression? -
bishop almost 10 years@echristopherson Without spaces,
test
sees only one argument (an "l-value"). Without the other arguments (an "operator" and an "r-value"), there is nothing to test, sotest
just says "ok, well yeah, you gave me something and that must be vacuously true". -
jdh8 almost 10 years
$1="1"
is$1
concatenated by=1
-
nyuszika7h almost 10 yearsCorrection: double quotes are only not needed on the left hand side!
[[ $foo == $bar ]]
will perform pattern matching, but[[ $foo == "$bar" ]]
won't. -
konsolebox almost 10 years@nyuszika7h Of course. Thanks for telling. I had only thought about the current subject. It's now updated.
-
Ruslan almost 10 years"Bash is not universal (and if you don't need universal, why are you writing a shell script?)" — this seems to imply that bash scripting has no use cases.
-
zwol almost 10 years@Ruslan Yes, that is my considered opinion. Write portable
/bin/sh
scripts, if you need that; otherwise, write in a scripting language that is less terrible than shell. The basic Perl interpreter is in fact more likely to be present in legacy proprietary environments and cut-down embedded environments than Bash is. -
dmckee --- ex-moderator kitten almost 10 yearsIn a sense this is the only the answer that has answered the question asked (rather than giving one or more recipes that do what the OP wanted to accomplish), and shell can be sufficiently mysterious that know why it does the things it does is useful.
-
Zen almost 10 yearswhen using case, how to set an action to execute when none of the conditions is meet?