2

I have this code:

sed \
$( (( $compress == 1 )) && echo -n '-e /^RMTHOST/ s/$/, compress/' ) \
-e "s|\*\*jobname\*\*|$jobname|g" \
-e "s|\*\*hostname\*\*|$hostname|g" \
-e "s|\*\*hostport\*\*|$hostport|g" \
-e "s|\*\*rmttrailname\*\*|$rmttrailname|g" < $GGPARAMSDIR/pump.template > 
$GGPARAMSDIR/$jobname.prm

that almost works like I want. If $compress == 1, I want the sed to include the string -e /^RMTHOST/ s/$/, compress/'. And if $compress != 1, don't include that section.

I'm getting the following error when $compress is 1

 sed: -e expression #1, char 10: missing command

When I add a set -x to the script for debugging it expands to the following:

sed -e '/^RMTHOST/' 's/$/,' compress/ -e 's|\*\*jobname\*\*|pssic|g' -e 's|\*\*hostname\*\*|omsssi|g' -e 's|\*\*hostport\*\*|7809|g' -e 's|\*\*rmttrailname\*\*|./dirdat/dsn/rc|g'

Notice the single ticks closing the first -e expression after the /^RMTHOST/, which I'm sure is the cause of my problem. But I can't figure out the syntax to fix it.

FYI, the values of the variables are jobname=pssic, hostname=omsssi, and hostport=7809

Can anyone help?

4
  • Same effect. the problem is not actually the test, its what is echo'ed after the true condition of the test. Commented Dec 21, 2017 at 1:21
  • Its some combination of altering the quotes, but that isn't it. Commented Dec 21, 2017 at 1:26
  • the '/' are part of the sed substitution. Commented Dec 21, 2017 at 1:28
  • If I change the line to: $( (( $compress == 1 )) && echo -n "-e '/^RMTHOST/ s/$/, compress/'" ) \ The result in set -x is: sed -e ''\''/^RMTHOST/' 's/$/,' 'compress/'\''' -e 's|**jobname**|pssic|g' -e 's|**hostname**|omsssi|g' -e 's|**hostport**|7809|g' -e 's|**rmttrailname**|./dirdat/dsn/rc|g' sed: -e expression #1, char 1: unknown command: `'' Commented Dec 21, 2017 at 1:33

2 Answers 2

4

The problem

The problem is that the result of a command substitution is subjected to both pathname expansion and word splitting.

To see what happens to the output of the command substitution, let's use printf to display the words that it produces:

$ printf ">%s<\n" $( (( compress == 1 )) && echo -n '-e /^RMTHOST/ s/$/, compress/' )
>-e<
>/^RMTHOST/<
>s/$/,<
>compress/<

You do need -e to be a separate word. Observe, however, the word-splitting also caused the sed substitute command to be broken up into something that sed will not understand:

$ sed -e '/^RMTHOST/' 's/$/,' 'compress/'
sed: -e expression #1, char 10: missing command

The solution

Try using bash arrays instead:

#!/bin/bash
jobname=pssic
hostname=omsssi
hostport=7809
compress=1
rmttrailname=SomethingElse

args=()
(( compress == 1 )) && args+=('-e' '/^RMTHOST/ s/$/, compress/')
args+=(
    -e "s|\*\*jobname\*\*|$jobname|g"
    -e "s|\*\*hostname\*\*|$hostname|g"
    -e "s|\*\*hostport\*\*|$hostport|g"
    -e "s|\*\*rmttrailname\*\*|$rmttrailname|g"
    )

declare -p args  # Optional: Verify the args are what we want.

sed "${args[@]}" <"$GGPARAMSDIR/pump.template" >"$GGPARAMSDIR/$jobname.prm"

Good reading

An interesting and more general discussion of the problems of creating commands out of shell variables is: "I'm trying to put a command in a variable, but the complex cases always fail!"

2
  • 1
    You nailed it. It works perfect. Many thanks. BF Commented Dec 21, 2017 at 1:47
  • As the tag is shell (not bash, ksh or zsh), a POSIX solution should be more appropriate. Commented Dec 21, 2017 at 2:47
0

A POSIX solution:

Unquoted command substitutions are subject to word splitting.

Change:

$( (( $compress == 1 )) && echo -n '-e /^RMTHOST/ s/$/, compress/' ) \

To (quote the expansion):

-e "$( [ "$compress" -eq 1 ] && printf '%s' '/^RMTHOST/ s/$/, compress/' )" \

That sets two arguments, one is -e and the other is the string sed needs.

Edited script:

#!/bin/sh

compress=$1
jobname=pssic
hostname=omsssi
hostport=7809
rmttrailname=ends
#set -x
echo "RMTHOST **jobname** **hostname** **hostport** **rmttrailname** test" | \
    sed \
    -e "$( [ "$compress" -eq 1 ] && printf '%s' '/^RMTHOST/ s/$/, compress/' )" \
    -e "s|\*\*jobname\*\*|$jobname|g" \
    -e "s|\*\*hostname\*\*|$hostname|g" \
    -e "s|\*\*hostport\*\*|$hostport|g" \
    -e "s|\*\*rmttrailname\*\*|$rmttrailname|g"
#set +x

And testing the code (without set -x):

$  ./so 1
RMTHOST pssic omsssi 7809 ends test, compress

If the set -x is uncommented:

$ ./so 1
+ echo RMTHOST **jobname** **hostname** **hostport** **rmttrailname** test
+ [ 1 -eq 1 ]
+ printf %s /^RMTHOST/ s/$/, compress/
+ sed -e /^RMTHOST/ s/$/, compress/ -e s|\*\*jobname\*\*|pssic|g -e s|\*\*hostname\*\*|omsssi|g -e s|\*\*hostport\*\*|7809|g -e s|\*\*rmttrailname\*\*|ends|g
RMTHOST pssic omsssi 7809 ends test, compress
+ set +x

Shells that use arrays

One completely different approach (and the best practice) is to accumulate all arguments as an array (only shells that have arrays):

#!/bin/bash
compress=$1
jobname=pssic
hostname=omsssi
hostport=7809
rmttrailname=ends

args=()
(( compress == 1 )) && args+=('-e' '/^RMTHOST/ s/$/, compress/')
args+=( -e "s|\*\*jobname\*\*|$jobname|g" )
args+=( -e "s|\*\*hostname\*\*|$hostname|g" )
args+=( -e "s|\*\*hostport\*\*|$hostport|g" )
args+=( -e "s|\*\*rmttrailname\*\*|$rmttrailname|g" )

echo "RMTHOST **jobname** **hostname** **hostport** **rmttrailname** test" | \
    sed "${args[@]}"

Running:

$ ./script 1
RMTHOST pssic omsssi 7809 ends test, compress

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.