3

I'm trying to write a database call from within a bash script and I'm having problems with a sub-shell stripping my quotes away.

This is the bones of what I am doing.

#---------------------------------------------    
#! /bin/bash    
export COMMAND='psql ${DB_NAME} -F , -t --no-align -c "${SQL}"  -o ${EXPORT_FILE} 2>&1'    
PSQL_RETURN=`${COMMAND}`    
#---------------------------------------------

If I use an 'echo' to print out the ${COMMAND} variable the output looks fine:

echo ${COMMAND}

screen output:-

#---------------    
psql drupal7 -F , -t --no-align -c "SELECT DISTINCT hostname FROM accesslog;" -o /DRUPAL/INTERFACES/EXPORTS/ip_list.dat 2>&1    
#---------------

Also if I cut and paste this screen output it executes just fine.

However, when I try to execute the command as a variable within a sub-shell call, it gives an error message. The error is from the psql client to the effect that the quotes have been removed from around the ${SQL} string. The error suggests psql is trying to interpret the terms in the sql string as parameters.

So it seems the string and quotes are composed correctly but the quotes around the ${SQL} variable/string are being interpreted by the sub-shell during the execution call from the main script.

I've tried to escape them using various methods: \", \\", \\\", "", \"" '"', \'"\', ... ... As you can see from my 'try it all' approach I am no expert and it's driving me mad.

Any help would be greatly appreciated.

Charlie101

2

2 Answers 2

4

Instead of storing command in a string var better to use BASH array here:

cmd=(psql ${DB_NAME} -F , -t --no-align -c "${SQL}" -o "${EXPORT_FILE}")
PSQL_RETURN=$( "${cmd[@]}" 2>&1 )
7
  • Hi anubhava. That worked perfectly and such a quick response. Your method is clearly better than using a string but I know nothing of shell arrays. Hence my frustration. Thanks again. Charlie101.
    – user108168
    Commented Aug 13, 2014 at 20:07
  • You're welcome, glad it worked. Read about BASH arrays
    – anubhava
    Commented Aug 13, 2014 at 20:09
  • Also if it worked for you can you mark the answer as accepted by clicking on tick mark on top-left of my answer.
    – anubhava
    Commented Aug 13, 2014 at 20:17
  • Why would you use an array in this case, when a function expresses your meaning better, is more portable and no more difficult to write?
    – Tom Fenech
    Commented Aug 13, 2014 at 20:20
  • 1
    @user108168, FYI, if you want to capture the content of the array in such a way you could paste it back into a command line and guarantee that it'll work the same way as "${cmd[@]}" would, that would be: printf '%q ' "${cmd[@]}"; echo -- without the printf '%q ' and the quotes what you'll get isn't always guaranteed to behave exactly as the command would when run. Commented Aug 14, 2014 at 14:54
2

Rather than evaluating the contents of a string, why not use a function?

call_psql() {    
    # optional, if variables are already defined in global scope
    DB_NAME="$1"
    SQL="$2"
    EXPORT_FILE="$3"

    psql "$DB_NAME" -F , -t --no-align -c "$SQL"  -o "$EXPORT_FILE" 2>&1
}

then you can just call your function like:

PSQL_RETURN=$(call_psql "$DB_NAME" "$SQL" "$EXPORT_FILE")

It's entirely up to you how elaborate you make the function. You might like to check for the correct number of arguments (using something like (( $# == 3 ))) before calling the psql command.

Alternatively, perhaps you'd prefer just to make it as short as possible:

call_psql() { psql "$1" -F , -t --no-align -c "$2"  -o "$3" 2>&1; }

In order to capture the command that is being executed for debugging purposes, you can use set -x in your script. This will the contents of the function including the expanded variables when the function (or any other command) is called. You can switch this behaviour off using set +x, or if you want it on for the whole duration of the script you can change the shebang to #!/bin/bash -x. This saves you explicitly echoing throughout your script to find out what commands are being run; you can just turn on set -x for a section.

A very simple example script using the shebang method:

#!/bin/bash -x

ec() {
  echo "$1"
}
var=$(ec 2)

Running this script, either directly after making it executable or calling it with bash -x, gives:

++ ec 2
++ echo 2
+ var=2

Removing the -x from the shebang or the invocation results in the script running silently.

5
  • Hi Tom Fenech. Thanks for your reply. Your reply worked perfectly as well. As I'm sure you know. I like the fact that it's nice and generic and can be made very simple as well. Nice and easy to understand. Is there an easy way to capture the actual command line output of the function so that I can use it in an error trap along with the $PSQL_RETURN message?
    – user108168
    Commented Aug 13, 2014 at 21:35
  • If you remove the redirection from the command, it should save the output to the variable. Is that what you wanted?
    – Tom Fenech
    Commented Aug 13, 2014 at 21:50
  • HI Tom If you mean the 2>&1 then no. As I'm sure you know this is redirecting standard error to standard out so that both will be assigned to the variable as in: PSQL_RETURN=$(call_psql). But this doesn't show me what the exact command call from the function was. Sure I can look at the code but small points are easy to miss unless you have the input variable unwrapped in the complete command. For anubhava's solution I can use: ${cmd[@]} to get the command and the return variable to get post execution output. Is there an equiv' of ${cmd[@]}? Thanks Charlie101
    – user108168
    Commented Aug 13, 2014 at 22:39
  • 1
    Hi Tom Fenech Because of the above requirement I'm going to mark anubhava as the solution. I think your is generally a very neat and clear generic solution but I need capture the exact command output for debugging purposes. Thanks for your help. I'm sure I'll use it elsewhere.
    – user108168
    Commented Aug 14, 2014 at 1:07
  • Now that I understand what you were trying to do, I've edited my answer to show you how you can use functions in the same way. I recommend that you read the link provided the comments above as it contains some useful information on this subject. If you decide to change your mind, you can still accept my answer.
    – Tom Fenech
    Commented Aug 14, 2014 at 7:22

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