1

I'm new to bash and want to write a simple program which takes an even amount of parameters. The script should always take two parameters and print them in one line as first an last name. A usage of the script could be:

getnames John Doe Max Muster Tim Stone

The output should be:

1. Person: John Doe
2. Person: Max Muster
3. Person: Tim Stone

The output I get right now:

::> getnames John Doe Max Muster Tim Stone
/home/user/bin/getnames: line 17: x: command not found
1. Person: John 
/home/user/bin/getnames: line 17: x: command not found
2. Person: Doe 
/home/user/bin/getnames: line 17: x: command not found
3. Person: Max 
/home/user/bin/getnames: line 17: x: command not found
4. Person: Muster 
/home/user/bin/getnames: line 17: x: command not found
5. Person: Tim 
/home/user/bin/getnames: line 17: x: command not found
6. Person: Stone 

Here is the code I've written so far:

#!/bin/bash

if [ $# -eq 0 ]; then
        echo "No names given"
        exit 1
elif [ $# -lt 2 ]; then
        echo "Please enter at least one first and one last name"
        exit 1
elif [ $(($#%2)) -ne 0 ]; then
        echo "Please always enter a first name and a last name for one person"
        exit 1
fi

count=1

for x in $*; do
        echo "$count. Person: $x $(x + 1)"
        let "x+=2"
        let count++
done
5
  • That looks good. What's the problem? Commented Apr 15, 2018 at 10:57
  • @MadPhysicist The only problem is the $(x + 1) which doesn't work so far. Commented Apr 15, 2018 at 10:59
  • You should probably mention that in the question and either post the error you are getting or compare the expected and the actual outputs. Commented Apr 15, 2018 at 11:03
  • @MadPhysicist I added the output I get right now to the post. Commented Apr 15, 2018 at 11:06
  • Thanks. The answer here should fix you right up. The only alternative I can think of is using eval to add a level of indirection to x but that is just not a good way to do it. It's also much more limited than shift. Commented Apr 15, 2018 at 11:09

4 Answers 4

3

use shift command.

if [ $# -eq 0 ]; then
        echo "No names given"
        exit 1
elif [ $# -lt 2 ]; then
        echo "Please enter at least one first and one last name"
        exit 1
elif [ $(($#%2)) -ne 0 ]; then
        echo "Please always enter a first name and a last name for one person"
        exit 1
fi

count=1

while (( "$#" )); do
        echo "$((count++)). Person: $1 $2"
        shift 2
done
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks, but how to do this in a for loop?
@TomStroub. Why does it have to be a for loop specifically?
@MadPhysicist Idk I'm just writing this for myself to get better in bash. This must be possible in a for loop or not? :/
@TomStrob. Oh it's possible, but I would argue that it wouldn't make you better at Bash. Sure you'd know of an arcane technique, but this answer is the simple way to do it.
You could write echo "$((count++))... and remove the let line. +1 though
|
3

Yet more ways:

  1. this doesn't quite give your desired output but is short and clear.

    #!/bin/bash
    printf "Person: %s %s\n" "$@" | nl -s ". "
    
  2. with a for-loop and using variable indirection

    for ((i=2; i<=$#; i+=2)); do 
        j=$((i-1))
        printf "%d. Person %s %s\n" $((i/2)) "${!j}" "${!i}"
    done
    

Comments

1

The easy way to do this is using shift, as shown in tso's answer. If you absolutely insist on doing it the hard way (using a for loop), you have a couple of alternatives neither of which I would recommend.

  1. Use printf command instead of echo to do your output. When count%2 is zero, print count/2, $x as the first name, and no newline. When count%2 is 1, print $x as the last name, and a newline. That's if count starts from zero.

    You probably want to loop over "$@" rather than $* even with your current setup. Some folks might have legitimate spaces in their name, like "Billy Bob" Thornton.

  2. You can run your loop over the numbers from 1 to $#, and use indirect variable referencing. IVR is using a variable to name another variable.

    The old and portable way to do indirection would be something like eval FIRST="\$$x" or FIRST="$(eval \$$x)". As of version 2.0, bash allows you to do FIRST="${!x}". This new form is not a standard notation between shells.

Comments

0

With a for loop

for x ; do
    let count++
    if [ $((count%2)) -eq 1 ] ; then
            name="$x"
    else
            echo "$((count/2)). Person: $name $x"
    fi
done

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.