71

I made two test bash scripts on Linux to make the problem clear.

TestScript1 looks like:

    echo "TestScript1 Arguments:"
    echo "$1"
    echo "$2"
    echo "$#"
    ./testscript2 $1 $2

TestScript2 looks like:

    echo "TestScript2 Arguments received from TestScript1:"
    echo "$1"
    echo "$2"
    echo "$#"

When I execute testscript1 in the following way:

    ./testscript1 "Firstname Lastname" testmail@example.com

The desired output should be:

    TestScript1 Arguments:
    Firstname Lastname
    testmail@example.com
    2
    TestScript2 Arguments received from TestScript1:
    Firstname Lastname
    testmail@example.com
    2

But the actual output is:

    TestScript1 Arguments:
    Firstname Lastname
    testmail@example.com
    2
    TestScript2 Arguments received from TestScript1:
    Firstname
    Lastname
    3

How do I solve this problem? I want to get the desired output instead of the actual output.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
nmadhok
  • 1,704
  • 3
  • 16
  • 20
  • in a nutshell: invoke the next command with : /path/to/TestScript2 "$@" (see my answer, below, to see how to write TestScript1 with this requirement in mind) – Olivier Dulac Jun 07 '13 at 16:45

4 Answers4

62

Quote your args in Testscript 1:

echo "TestScript1 Arguments:"
echo "$1"
echo "$2"
echo "$#"
./testscript2 "$1" "$2"
Markku K.
  • 3,840
  • 19
  • 20
  • I'm actually trying to invoke a salt-stack command which is salt '*mysql' cmd.run 'bash /opt/clemson/mysql/bin/test_mysql_db_create "$1" "$2" "$3" "$4" "$5" "$6"' The double quotes don't seem to work in this case. It worked when i had the single quotes for the most part but didn't work in the case i specified in the question. – nmadhok Jun 07 '13 at 16:22
  • 1
    Are you saying that your real question is different from the one that you asked? :) I have no idea what a "salt-stack" command is, but it looks like you may have a problem with multiple levels of quotes. You have $1 etc enclosed in double quotes, but the whole thing is inside single quotes -- if salt-stack acts like bash, this means that you will get literal "$1" "$2" etc passed to your bash command. Perhaps you should ask a new question that is more specific to your actual situation. – Markku K. Jun 07 '13 at 16:30
  • My real question is different in the way that i constructed the above two test scripts showing what problem i had faced. Maybe i misinterpreted the problem. The problem is that i have made a script that executes a command in the form of salt 'servername' cmd.run 'whatever command to execute'. whatever command to execute is what we write on the shell for example ./testscript. It has to be included in single quotes like this './testscript'. How do i get this part to work: './testscript $1 $2' that is the command is enclosed in single quotes and the double quotes aren't working inside that. – nmadhok Jun 07 '13 at 16:42
  • 4
    Yes, that is a different problem. You should ask a new question, with a snippet of code from your actual script. You can simplify the code that you post, but in this case you simplified it so much that it does not present the same problem that you are having. – Markku K. Jun 07 '13 at 17:03
42

You need to use : "$@" (WITH the quotes) or "${@}" (same, but also telling the shell where the variable name starts and ends).

(and do NOT use : $@, or "$*", or $*).

ex:

#testscript1:
echo "TestScript1 Arguments:"
for an_arg in "$@" ; do
   echo "${an_arg}"
done
echo "nb of args: $#"
./testscript2 "$@"   #invokes testscript2 with the same arguments we received

I'm not sure I understood your other requirement ( you want to invoke './testscript2' in single quotes?) so here are 2 wild guesses (changing the last line above) :

'./testscript2' "$@"  #only makes sense if "/path/to/testscript2" containes spaces?

./testscript2 '"some thing" "another"' "$var" "$var2"  #3 args to testscript2

Please give me the exact thing you are trying to do

edit: after his comment saying he attempts tesscript1 "$1" "$2" "$3" "$4" "$5" "$6" to run : salt 'remote host' cmd.run './testscript2 $1 $2 $3 $4 $5 $6'

You have many levels of intermediate: testscript1 on host 1, needs to run "salt", and give it a string launching "testscrit2" with arguments in quotes...

You could maybe "simplify" by having:

#testscript1

#we receive args, we generate a custom script simulating 'testscript2 "$@"'
theargs="'$1'"
shift
for i in "$@" ; do
   theargs="${theargs} '$i'"
done

salt 'remote host' cmd.run "./testscript2 ${theargs}"

if THAt doesn't work, then instead of running "testscript2 ${theargs}", replace THE LAST LINE above by

echo "./testscript2 ${theargs}" >/tmp/runtestscript2.$$  #generate custom script locally ($$ is current pid in bash/sh/...)
scp /tmp/runtestscript2.$$ user@remotehost:/tmp/runtestscript2.$$ #copy it to remotehost
salt 'remotehost' cmd.run "./runtestscript2.$$" #the args are inside the custom script!
ssh user@remotehost "rm /tmp/runtestscript2.$$" #delete the remote one
rm /tmp/runtestscript2.$$ #and the local one
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
Olivier Dulac
  • 3,695
  • 16
  • 31
  • I want the command to execute like i have specified but within single quotes. Like the following way: './testscript2'. How do i include command line arguments $1, $2 in that? – nmadhok Jun 07 '13 at 16:59
  • in the above post you mentino you want to invoke: `salt '*mysql' cmd.run 'bash /opt/clemson/mysql/bin/test_mysql_db_create "$1" "$2" "$3" "$4" "$5" "$6"'` : does it mean invoking "salt" with first arg '*mysql' , 2nd arg: cmd.run , 3rd arg: 'bash......' ? and so "salt" receives as 2nd arg a string containing "$1" "$2" (and NOT their value) ? or do you want to pass their values instead? – Olivier Dulac Jun 07 '13 at 17:13
  • I have two scripts. The second script accepts 7 arguments and creates a MySQL database. That script is working perfectly fine. Now i created another script to call that script through Salt Stack command. The command is salt 'target location' cmd.run 'command-goes-here'. Consider this example: salt '172.168.1.2' cmd.run 'ls /var/www/' so this would display me the contents of the folder /var/www present on the host 172.168.1.2. I basically want the user to invoke this script having this command which would invoke the other script that creates the database. – nmadhok Jun 07 '13 at 18:12
  • if the user would write ./testscript1 arg1 arg2 arg3 arg4 "arg 5" arg6, then it would invoke the script which would invoke the command salt 'remote host' cmd.run './testscript2 $1 $2 $3 $4 $5 $6'. What this command would do is that it would do whatever is present in the section after cmd.run present between ''. So basically it would invoke testscript2 with the arguments. The problem i'm having is that i'm not able to pass properly "Firstname Lastname" as the arg5. It's not getting passed completely and is getting split. When i tried './teststring2 "$1" "$2" "$3" "$4" "$5" "6"' it didn't work – nmadhok Jun 07 '13 at 18:18
  • Instead of calling it as salt 'remote host' cmd.run "./testscript2 ${theargs}", I want to call it as salt 'remote host' cmd.run './testscript2 ${theargs}'. cmd.run takes it's argument in single quotes. Will that work? – nmadhok Jun 09 '13 at 06:20
0

You could do the following to parse all arguments:

Note the use of /bin/bash

testscript1:

#!/bin/bash

echo "TestScript1 Arguments:"
echo "$1"
echo "$2"
echo "$#"

# Build PARAMS to be a list of arguments
# Each wrapped in quotes and
# any existing quotes escapes with \
for var in "$@"
do
    PARAMS="$PARAMS \"${var//\"/\\\"}\""
done

# Call the second script with eval, also passing an extra parameter.
eval ./testscript2 "ExtraParam" $PARAMS

testscript2:

#!/bin/bash
echo "TestScript2 Arguments received from TestScript1:"
echo "$1"
echo "$2"
echo "$3"
echo "$#"

Then when you execute the following:

./testscript1 "Firstname Lastname" testmail@example.com

The output will be:

TestScript1 Arguments:
Firstname Lastname
testmail@example.com
2
TestScript2 Arguments received from TestScript1:
ExtraParam
Firstname Lastname
testmail@example.com
3

By building the PARAMS in this way using \"${var//\"/\\\"}\"" it allows for the script to behave correctly even if called with quotes in the arguments such as follows:

./testscript1 "Firstname's Last\"name" testmail@example.com

Output will be:

TestScript1 Arguments:
Firstname's Last"name
testmail@example.com
2
TestScript2 Arguments received from TestScript1:
ExtraParam
Firstname's Last"name
testmail@example.com
3

All quotes and spaces are passed from one script to the other correctly.

If you don't want all the arguments passed, miss out the for loop and just use eval with the relevant arguments.

For example:

eval ./testscript2 "\"${1//\"/\\\"}\"" "Test1" "Test2"

${1... means the 1st argument, use ${2... for the 2nd etc.

This will result in:

TestScript1 Arguments:
Firstname's Last"name
testmail@example.com
2
TestScript2 Arguments received from TestScript1:
Firstname's Last"name
Test1
Test2
3

If you're sure you'll never get quotes in the argument, then don't use "\"${1//\"/\\\"}\"" just use "$1".

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
-2

I found following program works for me

test1.sh 
a=xxx
test2.sh $a

in test2.sh you use $1 to refer variable a in test1.sh

echo $1

The output would be xxx

ChrisF
  • 134,786
  • 31
  • 255
  • 325
Linsong Guo
  • 37
  • 1
  • 1
  • 6