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, the arguments of which are collected in quote as a single argument and passed to a perl script.

/usr/local/API/check_api.sh "-D x.x.x.x -C Test_Internal_Cluster -u user -p pass  -i 300 -l runtime -s list"

which result to

/usr/bin/perl /usr/local/check_api.pl -D x.x.x.x -C Test_Internal_Cluster -u user -p pass -i 300 -l runtime -s list

I want to give an argument like this:

"Test Internal Cluster"

to the perl script.

When I am using

"Test\ Internal\ Cluster"

as the argument to the shell script its parsing as

Test Internal Cluster

to the perl script, but I want that to be in double quotes.

If I am using this:

""\"Test Internal Cluster\"

It converting to:

'"Test' Internal 'Cluster"'

This seems simple but not able to use proper quoting and escaping to achieve.

The shell script is:

#!/bin/bash -xv

Perl=/usr/bin/perl
Api=/usr/local/API/check_api.pl

if [ $# -gt 0 ]; then
  if [ -x $Api ]
   then
        output=$($Perl $Api $1)
        exitstatus=$?
        F1=$(echo $output | awk -F':' '{print $1}')
        F2=$(echo $output | awk -F':' '{print $2}' | awk -F'|' '{print $1}')
        F3=$(echo $output | awk -F':' '{print $2}' | awk -F'|' '{print $2}')
        echo " $F1: $F3 | $F2"
  else
        echo "check_api.pl not found"
 fi
 exit $exitstatus
else
    echo "Usage : $0 '-D <DC> -C <Cluster> -u <userbame> -p <password> -i <interval> -l <command>  -s <subcommand>'"
fi

Here you can see the whole argument passed to the shell script is collected to $1 and passed to the check_api perl script.

The issue is happening with cluster name with space (passed to -C).

And the whole argument passed to the shell script should be in single/double quotes.

I haven't quoted the variable $1 because the perlscript is not accepting the whole argument as a single as below which I found in debug mode - where the whole argument is quoted if $1 is quoted.

/usr/bin/perl /usr/local/check_api.pl '-D x.x.x.x -C Test_Internal_Cluster -u user -p pass  -i 300 -l runtime -s list'

and throws error as incorrect usage

But without quoting the $1 the argument passed to perl script works like this and which gives me the result:

/usr/bin/perl /usr/local/check_api.pl -D x.x.x.x -C Test_Internal_Cluster -u user -p pass -i 300 -l runtime -s list

But I am failed with a cluster with space in its name.

In case of a space in cluster name if I am running in the command line directly(not from shell script) it works fine without any issue:

/usr/bin/perl /usr/local/check_api.pl -D x.x.x.x -C "Test Internal Cluster" -u user -p pass -i 300 -l runtime -s list

which I wanted to achieve from shell script.

share|improve this question
1  
Wrap it in single quotes? '"Test Internal Cluster"' –  jasonwryan Feb 25 at 7:00

5 Answers 5

You should use like this:

"\"Test Internal Cluster\""

or wrap it in single quote:

'"Test Internal Cluster"'

Test:

#!/bin/bash

echo $1

Output:

% bash test.sh "\"Test Internal Cluster\""
"Test Internal Cluster"

Update

The problem here is becasue you call perl with $1, $1 contains white space, so perl will see it as three separate arguments.

To prevent this, you should wrap $1 in double quotes "$1".

Another note is you should handle $1 argument in perl script, because if $1 contains any special characters, it will cause some bugs in perl program. you can use quotemeta($ARGV[0]).

Note

I think you shoul not pass all argument as a string to shell script. You should pass them as normal, except cluster name, you still have to wrap it as string. Then pass them to perl script by "$@" instead of $1.

share|improve this answer
    
Hi its ok with echo echo. But when I am using it as argument to shell script : in debug mode I found up to '"Test' and rest are broken . –  kumarprd Feb 25 at 7:16
    
Please give your shell script. –  cuonglm Feb 25 at 7:17
    
shell script updated with question. –  kumarprd Feb 25 at 7:25
    
Update answer for this problem. –  cuonglm Feb 25 at 7:43
1  
How you pass arguments to shell script? –  cuonglm Feb 25 at 9:16

I'm having a little trouble following your question, and I think that both your concrete problem and the odd phrasing of your question stem from the same root, which is an incorrect model of how command argument parsing works.

A command does not receive a string as an argument, but a list of strings. All the quoting is performed by the shell.

If you run any of the following shell commands

mycommand "foo bar" wibble
mycommand 'foo bar' 'wibble'
mycommand ''''foo" "bar \wib\ble
mycommand       foo\ bar \
                "wibble"

(or a myriad variants) then the command is executed with exactly the same arguments in all cases:

  • argument 0 is mycommand;
  • argument 1 is the 7-character string foo bar;
  • argument 2 is the 6-character string wibble.

The shell construct $1 does not mean “take the value of parameter 1”. It means “take the value of parameter 1, split it into whitespace-separated pieces, and interpret each piece as a wildcard pattern and replace it by the names of matching files”. Yes, it's quite a mouthful. As a general shell programming principle, always put double quotes around variable substitutions and command susbtitutions: "$1", "$some_variable", "$(some_command)".

Here, two wrongs make a right, but only in a narrow case: you pass a single argument to the shell script, and split is into whitespace-separated pieces to turn it into a list of strings to pass as arguments to the Perl script. This is an unintuitive and limited interface. It doesn't allow you to have spaces in the arguments to the Perl script, since spaces are already used to separate arguments. You can't have it both ways. Quotes are of no help here: you can pass a quote character in the argument to the shell script, and it'll become part of an argument to the Perl script. For example, if you run one of

check_api.sh "-C \"Test Internal Cluster\""
check_api.sh '-C "Test Internal Cluster"'
check_api.sh -C\ \"Test Internal Cluster\"

then the shell script is called with a single argument -C "Test Internal Cluster", and the Perl script is called with the 4 arguments -C, "Test, Internal and Cluster".

The fix is simple, but it does require changing the calling convention for the shell script. What you have now is a bug, you need to fix it. Call the script the normal way:

/usr/local/API/check_api.sh -D x.x.x.x -C Test_Internal_Cluster -u user -p pass  -i 300 -l runtime -s list
/usr/local/API/check_api.sh -D x.x.x.x -C 'Test Internal Cluster' -u user -p pass  -i 300 -l runtime -s list
/usr/local/API/check_api.sh -D x.x.x.x -C "Test Internal Cluster" -u user -p pass  -i 300 -l runtime -s list

(the last two are equivalent, since single and double quotes only make a difference when \$` appear inside7), and fix the shell script to use double quotes around command substitutions.

if [ -x "$Api" ]
then
    output=$("$Perl" "$Api" "$1")
    exitstatus=$?
    F1="${output%%:*}"; output="${output#*:}"
    output="${output%%:*}"
    F2="${output%%\|*}"; output="${output#*\|}"
    F3="${output%%\|*}"
fi

I've replaced the awk code by equivalent shell constructs, assuming that the output is a single line.

share|improve this answer

The problem is in the following line:

output=$($Perl $Api $1)

You need to quote the argument:

output=$($Perl $Api "$1")

When you don't quote the variable, word-splitting causes perl to see multiple arguments.

share|improve this answer
4  
You need to quote all the variables (output=$("$Perl" "$Api" "$1") and [ -x "$Api" ]...). Leaving a variable unquoted is the split+glob operator in shells. It makes no sense to use that operator here. –  Stéphane Chazelas Feb 25 at 8:14

The problem, really, is that you should be doing proper option parsing. It looks like maybe you don't know how, but there's no shame in that.

Using bash's built in option parsing, and properly quoting them along the way will make it all work like you want it to, meaning that you can quote it normally on the command line. No need to re-quote or escape anything.

#!/bin/bash

PERL=/usr/bin/perl
API=/usr/local/API/check_api.pl

usage() {
    echo "Usage : $0 -D <DC> -C <Cluster> -u <userbame> -p <password> -i <interval> -l <command>  -s <subcommand>"
    exit 1
}

while getopts "C:D:u:p:i:l:s:" options; do
    case $options in
        C ) CLUSTER="${OPTARG}";;
        D ) DC="${OPTARG}";;
        u ) USER="${OPTARG}";;
        p ) PASS="${OPTARG}";;
        i ) INTERVAL="${OPTARG}";;
        l ) COMMAND="${OPTARG}";;
        s ) SUBCOMM="${OPTARG}";;
        * ) usage ;;
    esac
done

$PERL $API -C "$CLUSTER" -D "$DC" -u "$USER" -p "$PASS" -i "$INTERVAL" -l "$COMMAND" -s "$SUBCOMM"

It looks like you're doing some other stuff with the output, which I'll leave to you to append to this example.

share|improve this answer
    
Initially I started with getopts and its there already with a lot of options. I know very well the use of getopts. But I was advised to make it short with this approach. And now its already in production and issue arised when someone created a cluster with space. So instead of changing inside the code, if with passing argument with quotes and escapes it can be corrected, it will better, thats why I asked it here. –  kumarprd Feb 25 at 9:37
    
Well, I can't account for someone else giving you bad advice. What you're doing is both difficult to understand and prone to error. You're directly experiencing both. You don't seem to be saving any time or effort in trying to hack it. I urge you to push back against whoever gave you that advice. –  bahamat Feb 25 at 9:44

The problem is that you try to use a single variable to store a list of strings:

I have a shell script, the arguments of which are collected in quote as a single argument and passed to a perl script.

That doesn't work. Once you concatenate several arguments and get a single string, the distinction between spaces within arguments and spaces between arguments is lost.There is no way to fix this by including quoting characters within the string, because, simply said, quoting characters lose their magic as soon as they are part of a string. A backslash or " within a string is an ordinary character; it doesn't quote anything.

Solution: Use an array variable to collect the arguments:

A=()
A+=(-D "$address")
A+=(-C "$clustername")
A+=(-u "$username")
A+=(-p "$password")
...
output=$("$Perl" "$Api" "${A[@]}")

(note quoting.)

Of course, if the arguments that you want to pass to the perl program are just all the command line arguments that are passed to your script, then they are already contained in an array (namely the list of command line arguments "$@"), so you can simply use that one:

output=$("$Perl" "$Api" "$@")
share|improve this answer

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.