Find the answer to your Linux question:
Results 1 to 10 of 10
I am trying to write a wrapper for a program (cvs) using bash. At the end of the script, I need to call the original program with the command-line arguments ...
  1. #1
    Just Joined!
    Join Date
    Jun 2006
    Posts
    5

    bash script problem: preserving quotes from arguments



    I am trying to write a wrapper for a program (cvs) using bash. At the end of the script, I need to call the original program with the command-line arguments that were passed to the script ($@). The problem is that some of the arguments have multiple words inside quotes (such as -m "this is a message"). If I do "program $@", then the quotes aren't passed to the program, and it can't parse the options. I have tried putting quotes around $@, and using $* as well, but I couldn't get bash to do what I want. Any suggestions?

    Here is the code in case it will help you. Even though I save the arguments in $OPTIONS, I get the same results when using $@ or $* directly.
    Code:
    #!/bin/sh
    
    #cvs [ cvs_options ] cvs_command [ command_options ] [ command_args ]
    
    OPTIONS="$*"
    
    # Strip off cvs_options
    until [ -z "$1" ]
    do
            if [ "${1:0:1}" = "-" ] ; then
                    if [ $1 = "-b" -o $1 = "-d" -o $1 = "-e" -o $1 = "-z" ] ; then
                            shift
                    fi
                    shift
                    continue
            fi
            break
    done
    if [ -z "$1" ] ; then
            cvs $OPTIONS    #No options? Let CVS deal with it...
            exit $?
    fi
    
    # Check cvs_command, execute it if not commit
    if [ "$1" != "commit" ] ; then
            cvs $OPTIONS
            exit $?
    fi
    
    # Check if files were given
    #[-lnR] [-m 'log_message' | -f file] [-r revision] [files...]
    shift
    until [ -z "$1" ]
    do
            if [ "${1:0:1}" = "-" ] ; then
                    if [ $1 = "-f" -o $1 = "-r" -o $1 = "-m" ] ; then
                            shift
                    fi
                    shift
                    continue
            fi
            break
    done
    if [ ! -z "$1" ] ; then
            cvs $OPTIONS
            exit $?
    fi
    
    # Read cvs status
    FILES=`cvs -n -q update`
    
    # Print warning
    echo "Your commit will include/exclude the following files:"
    echo "$FILES"
    echo
    
    # Get answer
    ans="?"
    until [ "$ans" = "y" -o "$ans" = "n" ]
    do
            echo "Are you sure you want to commit? [y/n]"
            read ans
    done
    
    # Perform command if "y"
    if [ "$ans" = "n" ] ; then
            echo "Not executing commit (aren't you happy you used this script?)"
    else
            cvs $OPTIONS
    fi

  2. #2
    Linux Newbie birdman's Avatar
    Join Date
    Mar 2006
    Location
    Ireland
    Posts
    141
    Could you use $1, $2 etc and build up $OPTIONS using a for loop and some double quotes? (You will have to escape the double quotes inside OPTIONS with a \)

    All the best

  3. #3
    Just Joined!
    Join Date
    Jun 2006
    Posts
    5
    Quote Originally Posted by birdman
    Could you use $1, $2 etc and build up $OPTIONS using a for loop and some double quotes? (You will have to escape the double quotes inside OPTIONS with a \)

    All the best
    It's a pain, and I didn't think it would work, so I didn't try. I'll give it a shot though. Will update with results.

    Thanks.

  4. #4
    Just Joined!
    Join Date
    Jun 2006
    Posts
    5
    I managed to recreate $OPTIONS. It's pretty hacky, but maybe it's good enough. When I print $OPTIONS, I see the quotes where they should be. Unfortunately, when I execute `cvs $OPTIONS`, it still behaves as if the quotes weren't there. Here is the code that I added to the beginning of the script:

    Code:
    countargs () {
            return $#
    }
    
    for (( i = 1; i <= $# ; i++ ))
    do
            eval VAR=\$$i
            countargs $VAR
            words=$?
            if [ "$words" -ge "2" ] ; then
                    OPTIONS="$OPTIONS \"$VAR\""
            else
                    OPTIONS="$OPTIONS $VAR"
            fi
    done

  5. #5
    Linux Newbie birdman's Avatar
    Join Date
    Mar 2006
    Location
    Ireland
    Posts
    141
    Does it work when you type in the parameters using double quotes without the script? (i haven't used cvs before)

    You could try single quotes also - apart from that I am a little stuck as to why this wouldn't work.

  6. #6
    Just Joined!
    Join Date
    Jun 2006
    Posts
    5
    Quote Originally Posted by birdman
    Does it work when you type in the parameters using double quotes without the script? (i haven't used cvs before)

    You could try single quotes also - apart from that I am a little stuck as to why this wouldn't work.
    Single quotes didn't work either. Here is a similar script, grepwrap.sh, that uses grep instead of cvs:

    Code:
    #!/bin/sh
    
    countargs () {
            return $#
    }
    
    for (( i = 1; i <= $# ; i++ ))
    do
            eval VAR=\$$i
            countargs $VAR
            words=$?
            if [ "$words" -ge "2" ] ; then
                    OPTIONS="$OPTIONS \"$VAR\""
            else
                    OPTIONS="$OPTIONS $VAR"
            fi
    done
    
    echo $OPTIONS
    grep $OPTIONS
    Here is what happens from the command line:
    $ echo "hello 1 there" > foo
    $ ./grepwrap.sh "hello there" foo
    "hello there" foo
    grep: there": No such file or directory
    $ grep "hello there" foo
    $

    So the quotes are in OPTIONS now, but they aren't being interepreted correctly.

  7. #7
    Linux Newbie birdman's Avatar
    Join Date
    Mar 2006
    Location
    Ireland
    Posts
    141
    Looked at this and cannot find an answer (I have even tried hex and octal but to no avail)

    I was looking for some way to see if we can store the not already interpreted double quotation mark, as I believe your problem lies in the fact that the double quote is already text and is interpreted by the shell as such. This is tough.

    Any bash gurus out there?

  8. #8
    drl
    drl is offline
    Linux Engineer drl's Avatar
    Join Date
    Apr 2006
    Location
    Saint Paul, MN, USA / CentOS, Debian, Solaris, SuSE
    Posts
    1,065
    Hi.

    I think $@ will do what you wish, but you'll need to invoke the magic string that includes quotes, namely
    Code:
    "$@"
    Here's a driver script and a small script that simulates what I take your script to be. I use ":" to put markers around strings so that I can see the boundaries, including spaces:
    Code:
    #!/bin/bash
    
    # @(#) drive    demo arg processing.
    
    ./s1 hello
    
    ./s1 "hello world"
    
    ./s1 "first phrase" "second phrase"
    And here is script it calls
    Code:
    #!/bin/bash
    
    # @(#) s1       Show argument processing.
    
    echo
    echo " Args plain"
    echo :$*:
    
    echo
    echo ' Args from "$*" (note quotes)'
    i=1
    for arg in "$*"
    do
            echo "arg $i is :$arg:"
            let "i+=1"
    done
    
    echo
    echo ' Args from "$@" (note quotes)'
    echo :"$@":
    i=1
    for arg in "$@"
    do
            echo "arg $i is :$arg:"
            let "i+=1"
    done
    The output
    Code:
    % ./drive 
    
     Args plain
    :hello:
    
     Args from "$*" (note quotes)
    arg 1 is :hello:
    
     Args from "$@" (note quotes)
    :hello:
    arg 1 is :hello:
    
     Args plain
    :hello world:
    
     Args from "$*" (note quotes)
    arg 1 is :hello world:
    
     Args from "$@" (note quotes)
    :hello world:
    arg 1 is :hello world:
    
     Args plain
    :first phrase second phrase:
    
     Args from "$*" (note quotes)
    arg 1 is :first phrase second phrase:
    
     Args from "$@" (note quotes)
    :first phrase second phrase:
    arg 1 is :first phrase:
    arg 2 is :second phrase:
    See positional argument section in man bash for more details. If necessary in your situation, you could then enclose $1, $2 in quotes to preserve the spaces to use as arguments to another script ... cheers, drl
    Welcome - get the most out of the forum by reading forum basics and guidelines: click here.
    90% of questions can be answered by using man pages, Quick Search, Advanced Search, Google search, Wikipedia.
    We look forward to helping you with the challenge of the other 10%.
    ( Mn, 2.6.n, AMD-64 3000+, ASUS A8V Deluxe, 1 GB, SATA + IDE, Matrox G400 AGP )

  9. #9
    Just Joined!
    Join Date
    Jun 2006
    Posts
    5

    Thanks

    "$@" did the trick. I was still unable to reconstruct it properly, so I used a counter for the parameters instead of shifting and using $1. Whenever I had to call cvs, I did:
    Code:
    cvs "$@"
    Thanks to both of you for your help.

  10. #10
    Just Joined!
    Join Date
    Feb 2009
    Posts
    1

    I needed argument escaping

    I was writing an SSH wrapper script, and for the life of me could not get quoted arguments to be passed through properly.

    I finally found a solution involving awk and escaping EVERY character of EVERY argument:

    Code:
    #!/bin/sh
    CMD=""
    
    for (( i = 1; i <= $# ; i++ )); do
      eval ARG=\$$i
      CMD="$CMD $(echo "$ARG" | awk '{gsub(".", "\\\\&");print}')"
    done
    
    ssh my_machine cd /my/path \&\& RAILS_ENV=production $CMD
    Surprisingly, this is quite bullet broof, and properly escapes strings and preserves arguments like:

    ./remote.sh grep "hello there" . -R
    ./remote.sh grep "So I says to the typewriter, \"Hey, I'm in quotes\"" . -R
    ./remote.sh grep "\"" . -R
    Last edited by timcharper; 02-21-2009 at 12:15 AM. Reason: Clarification

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •