Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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?

share|improve this question
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. –  nyuszika7h Aug 8 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 at 2:49
    
$(cmd) is command substitution, (mostly) the same as `cmd`. See mywiki.wooledge.org/CommandSubstitution and mywiki.wooledge.org/BashFAQ/082 –  nyuszika7h Aug 9 at 7:56

4 Answers 4

up vote 17 down vote accepted

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
share|improve this answer
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? –  echristopherson Aug 8 at 22:35
3  
@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 at 23:56
1  
$1="1" is $1 concatenated by =1 –  jdh8 Aug 9 at 7:03
    
when using case, how to set an action to execute when none of the conditions is meet? –  Zen Aug 20 at 9:56

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.

share|improve this answer
    
shouldn't you break in each case ? –  Archemar Aug 8 at 11:24
1  
@Archemar: no, there's no fall-through (contrary to a lot of other languages). –  Mat Aug 8 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. –  Stéphane Chazelas Aug 8 at 12:10
    
That's not arguments to if but to the [ command. –  Stéphane Chazelas Aug 8 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. –  nyuszika7h Aug 9 at 8:01

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.

share|improve this answer
1  
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. –  dmckee Aug 9 at 16:16

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

share|improve this answer
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 at 11:04
    
@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. –  Zack Aug 9 at 12:36

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.