0

I am executing a chain of curl commands:

  1. I need to echo the command before the execution.
  2. Execute the command and save the result to a bash variable.
  3. Get values from the result of the execution and execute the next curl with that values.

This is how it looks like:

#  -----> step 1 <-----
URL="https://web.example.com:8444/hello.html"
CMD="curl \
        --insecure \
        --dump-header - \
        \"$URL\""

echo $CMD && eval $CMD
OUT="<result of the curl command???>"

# Set-Cookie: JSESSIONID=5D5B29689EFE6987B6B17630E1F228AD; Path=/; Secure; HttpOnly
JSESSIONID=$(echo $OUT | grep JSESSIONID | awk '{ s = ""; for (i = 2; i <= NF; i++) s = s $i " "; print s }' | xargs)

# Location: https://web.example.com:8444/oauth2/authorization/openam
URL=$(echo $OUT | grep Location | awk '{print $2}')

#  -----> step 2 <-----
CMD="curl \
        --insecure \
        --dump-header - \
        --cookie \"$JSESSIONID\" \
        \"$URL\""
echo $CMD && eval $CMD
OUT="<result of the curl command???>"
...

#  -----> step 3 <-----
...

I only have a problem with the step 2: save the full result of the curl command to a variable in order to I can parse it.

I have tried it many different way, non of them works:

  • OUT="eval \$CMD"
  • OUT=\$$CMD
  • OUT=$($CMD)
  • ...

What I missed?

zappee
  • 20,148
  • 14
  • 73
  • 129
  • 2
    Storing nontrivial commands (like, anything that involves quotes) in variables is difficult at best, and your best option is to find a different way to accomplish what you want. See [BashFAQ #50: I'm trying to put a command in a variable, but the complex cases always fail!](http://mywiki.wooledge.org/BashFAQ/050) BTW, `eval` is sometimes given as an option for this sort of thing, but it tends to be its own Pandora's box of weird bugs, so I recommend avoiding it if at all possible. – Gordon Davisson Sep 24 '21 at 19:44
  • @GordonDavisson Thx for the advise. I have read that on many posts on stackoverflow but that was the only one way how I could make the `echo` and `execution` work. I am not a bash superhero so I just left it that way after a couple of try. Any improvement very welcomed. – zappee Sep 24 '21 at 19:53
  • In general, there's never a good reason to `echo $CMD`. If you want to log the commands you're running, just use `set -x`. That sends the log in question to stderr, so command substitutions won't capture it. – Charles Duffy Sep 24 '21 at 19:57
  • If you haven't read Gordon's link to BashFAQ #50, by the way, _do that_. – Charles Duffy Sep 24 '21 at 19:57
  • 1
    BTW, `echo $OUT` _itself_ adds a bunch of bugs. See [I just assigned a variable, but `echo $variable` shows something else!](https://stackoverflow.com/questions/29378566/i-just-assigned-a-variable-but-echo-variable-shows-something-else) – Charles Duffy Sep 24 '21 at 19:58
  • 1
    As another aside, there's basically never a good reason to pipe `grep | awk`: `awk` itself can do everything grep can, and much more as well (think of awk like perl or python: it's a fully capable programming language in and of itself). `awk '/Location/ { print $2 }'`, for example, lets you drop the `grep Location`. – Charles Duffy Sep 24 '21 at 20:00
  • @GordonDavisson, ...btw, I'm curious -- did you reopen this? I wrote an answer for it despite thinking it's a duplicate because clearly _some_ other gold-badge holder thinks that it's not duplicative, but I have trouble seeing how. – Charles Duffy Sep 24 '21 at 20:13
  • @CharlesDuffy Nope, I just commented; I'm pretty sure there's a good dupe around somewhere, but most of the ones on my list have to do with constructing a command, not printing it, so I didn't mark or vote on it at all. – Gordon Davisson Sep 24 '21 at 20:24

3 Answers3

5

For very basic commands, OUT=$($CMD) should work. The problem with this is, that strings stored in variables are processed differently than strings entered directly. For instance, echo "a" prints a, but var='"a"'; echo $a prints "a" (note the quotes). Because of that and other reasons, you shouldn't store commands in variables.

In bash, you can use arrays instead. By the way: The naming convention for regular variables is NOT ALLCAPS, as such names might accidentally collide with special variables. Also, you can probably drastically simplifiy your grep | awk | xargs.

url="https://web.example.com:8444/hello.html"
cmd=(curl --insecure --dump-header - "$url")
printf '%q ' "${cmd[@]}"; echo
out=$("${cmd[@]}")
# Set-Cookie: JSESSIONID=5D5B29689EFE6987B6B17630E1F228AD; Path=/; Secure; HttpOnly
jsessionid=$(awk '{$1=""; printf "%s%s", d, substr($0,2); d=FS}' <<< "$out")
# Location: https://web.example.com:8444/oauth2/authorization/openam
url=$(awk '/Location/ {print $2}' <<< "$out")

#  -----> step 2 <-----
cmd=(curl --insecure --dump-header - --cookie "$jsessionid" "$url")
printf '%q ' "${cmd[@]}"; echo
out=$("${cmd[@]}")

#  -----> step 3 <-----
...

If you have more steps than that, wrap the repeating part into a function, as suggested by Charles Duffy.

Socowi
  • 25,550
  • 3
  • 32
  • 54
  • Insofar as `echo "${cmd[*]}"` isn't guaranteed to write something that can be copied-and-pasted to run the same command as `"${cmd[@]}"`, it strikes me as risky to teach. `cmd=( printf '%s\n' "Never run the following:" '$(rm -rf ~)' )`, for example, makes `"${cmd[@]}"` safe, but `eval "${cmd[*]}"` (an equivalent to the aforementioned copy-and-paste of `echo "${cmd[*]}"`) dangerous. – Charles Duffy Sep 24 '21 at 20:14
  • @CharlesDuffy Thanks for the hint. I haven't thought about safety in terms of humans interpreting the output by hand. I changed it to `printf '%q ' "${cmd[*]}"; echo`. – Socowi Sep 24 '21 at 20:16
2

Easy Mode: Use set -x

Bash has a built-in feature, xtrace, which tells it to log every command to the file descriptor named in the variable BASH_XTRACEFD (by default, file descriptor 2, stderr).

#!/bin/bash
set -x
url="https://web.example.com:8444/hello.html"
output=$(curl \
  --insecure \
  --dump-header - \
  "$url")

echo "Output of curl follows:"
echo "$output"

...will provide logs having the form of:

+ url=https://web.example.com:8444/hello.html
++ curl --insecure --dump-header - https://web.example.com:8444/hello.html
+ output=Whatever
+ echo 'Output of curl follows:'
+ echo Whatever

...where the + is based on the contents of the variable PS4, which can be modified to have more information. (I often use and suggest PS4=':${BASH_SOURCE}:$LINENO+' to put the source filename and line number in each logged line).

Doing It By Hand

If that's not acceptable, you can write a function.

log_and_run() {
  { printf '%q ' "$@"; echo; } >&2
  "$@"
}

output=$(log_and_run curl --insecure --dump-header - "$url")

...will write your curl command line to stderr before storing its output in $output. Note when writing that output that you need to use quotes: echo "$output", not echo $output.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
-1

I guess OUT=$(eval $CMD) will do what you want.

  • Unfortunately this saves only the last line of the result. Not the whole one with multiply lines. – zappee Sep 24 '21 at 19:45
  • 1
    `eval $CMD` adds a _lot_ of surprising bugs. Try embedding `printf ' - %s\n' "first line" "*" "second line"` in `CMD` if you want to test a corner case that exposes a lot of the problems with the approach. – Charles Duffy Sep 24 '21 at 19:56
  • 1
    @zappee, btw, while this has other bugs, it should indeed save the whole result. However, if you `echo $OUT` you may not see that result even if it was saved: If the document returned by curl uses DOS-style newlines, the CRs when printed send the cursor back to the front of the line so when the next content gets printed it overwrites it. Typically the presence of LFs makes that harmless because a LF makes the cursor move down a line (so the prior line isn't overwritten), but when you use `echo $OUT` instead of `echo "$OUT"` with a default value of IFS, those LFs get changed to spaces. – Charles Duffy Sep 24 '21 at 21:29
  • (that's why I linked to [I just assigned a variable but `echo $variable` shows something else](https://stackoverflow.com/questions/29378566/i-just-assigned-a-variable-but-echo-variable-shows-something-else) in a comment on the question). – Charles Duffy Sep 24 '21 at 21:30
  • thanks a lot for you helping, I learned a lot today – zappee Sep 24 '21 at 21:48