10

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?

3
  • 1
    +1 for lab, Sun, Moon, Earth and teleport. But you should always double quote expansions ($var, $(cmd), and even `cmd` [to which $(cmd) should be preferred]). There are some edge cases where you don't have to quote, but always doing it won't hurt. Aug 8, 2014 at 22:27
  • @nyuszika7h, shouldn't double quote means "$var" and "$cmd"? what's the benefit of the round bracket you've mentioned above?
    – Zen
    Aug 9, 2014 at 2:49
  • $(cmd) is command substitution, (mostly) the same as `cmd`. See mywiki.wooledge.org/CommandSubstitution and mywiki.wooledge.org/BashFAQ/082 Aug 9, 2014 at 7:56

4 Answers 4

20

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
4
  • 3
    Yes, 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? Aug 8, 2014 at 22:35
  • 6
    @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, so test just says "ok, well yeah, you gave me something and that must be vacuously true".
    – bishop
    Aug 8, 2014 at 23:56
  • 2
    $1="1" is $1 concatenated by =1
    – jdh8
    Aug 9, 2014 at 7:03
  • when using case, how to set an action to execute when none of the conditions is meet?
    – Zen
    Aug 20, 2014 at 9:56
11

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.

7
  • shouldn't you break in each case ?
    – Archemar
    Aug 8, 2014 at 11:24
  • 2
    @Archemar: no, there's no fall-through (contrary to a lot of other languages).
    – Mat
    Aug 8, 2014 at 11:34
  • 2
    @Mat, there's fall-through if you use ;& instead of ;; (ksh, bash, zsh only). But then break still does not prevent the fall-through, break is only to break out of loops. Aug 8, 2014 at 12:10
  • That's not arguments to if but to the [ command. Aug 8, 2014 at 12:10
  • 1
    Correction: double quotes are only not needed on the left hand side! [[ $foo == $bar ]] will perform pattern matching, but [[ $foo == "$bar" ]] won't. Aug 9, 2014 at 8:01
7

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.

1
  • 3
    In 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. Aug 9, 2014 at 16:16
1

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.)

2
  • 1
    "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.
    – Ruslan
    Aug 9, 2014 at 11:04
  • 1
    @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.
    – zwol
    Aug 9, 2014 at 12:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .