0

This is the script that is in my Jenkins build step:

echo "
echo \"==== Tagging this version ====\"
git checkout master
git pull
git tag -a $projectVersion -m \"version $projectVersion\"
git push origin $projectVersion

echo \"==== Cleaning up merged branches ====\"
git branch -r --merged | grep -v \\* | grep -v master | grep -v dmz | grep -v develop | while read line; do git push origin :\${line//origin\\//}; done

echo \"==== Merging release changes into develop and dmz ====\"
git checkout develop
git pull
git merge master
git push origin develop
git checkout dmz
git pull
git merge develop
git push origin dmz
git checkout master
"

The first echo " and the last " are trying to spit out whats in the middle as instructions to be run manually, but the $projectVersion variable substituted in. If I copy that code and paste it into my terminal it runs and prints fine. However, Jenkins is doing something funny with double quote multi-line strings, because the build keeps failing with this:

19:31:30 echo 
19:31:30 "==== Tagging this version ===="
19:31:30 Switched to a new branch 'master'
19:31:30 Branch master set up to track remote branch master from origin.
19:31:31 Already up-to-date.
19:31:31 fatal: Failed to resolve '1.0.3"' as a valid ref.
19:31:31 Build step 'Execute shell' marked build as failure

It looks like it is completely ignoring the " right after the first echo which it should not be doing.

I read in multiple places that I need to use printf inside ``, inside an echo "" statement, so I tried that but got lost with trying to escape everything:

echo "`printf \"echo \\"==== Tagging this version ====\\"\ngit checkout master\ngit pull\ngit tag -a $projectVersion -m \\"version $projectVersion\\"\ngit push origin $projectVersion\n\necho \\"==== Cleaning up merged branches ====\\"\ngit branch -r --merged | grep -v \\\* | grep -v master | grep -v dmz | grep -v develop | while read line; do git push origin :\\${line//origin\\\//}; done\n\necho \\"==== Merging release changes into develop and dmz ====\\"\ngit checkout develop\ngit pull\ngit merge master\ngit push origin develop\ngit checkout dmz\ngit pull\ngit merge develop\ngit push origin dmz\ngit checkout master\\"`"
wheeler
  • 2,823
  • 3
  • 27
  • 43
  • I don't know anything about Jenkins, but try putting a backslash at the end of each line. – Barmar Oct 25 '17 at 23:57
  • @Barmar Good thought, but sadly that didn't work. All I get as a result is: `20:00:35 /var/appl/jenkins/RC/tmp/hudson2707907294587456168.sh: line 35: unexpected EOF while looking for matching '"'` – wheeler Oct 26 '17 at 00:01
  • This is a job for a heredoc. – Charles Duffy Oct 26 '17 at 00:05
  • though all the stray backslashes next to your literal quotes make it harder. Do you have a version from before you added them? – Charles Duffy Oct 26 '17 at 00:06
  • BTW, whatever source told you to use printf inside a command substitution was utter garbage. If it was a SO answer, could you please link it? – Charles Duffy Oct 26 '17 at 00:16
  • [Here](https://stackoverflow.com/questions/37593485/jenkins-shell-string-quotation-replacement) it is. However, before passing judgement, just know that the problem was slightly different in that context and so the solution might be appropriate for that one even if it weren't for mine. – wheeler Oct 26 '17 at 00:20
  • Ahh -- so the "inside an echo statement" part isn't taken from the remote answer. Big difference there; `echo "$(foo)"` is functionally equivalent to just running `foo`, except a lot slower (unless `foo` changes its behavior on whether output is to a TTY or some other odd corner cases, but in general). – Charles Duffy Oct 26 '17 at 00:25
  • Yeah, I'm not reading things to completion and instead just trying things out for a quick and dirty solution until an internal org issue is resolved. – wheeler Oct 26 '17 at 00:26

3 Answers3

1

Approach One: Heredocs

If you want to dump a block of content to stdout, a heredoc is the right tool for the job. This one assumes you want $projectVersion replaced with that variable's value (though note that doing such replacement in a code-generation context generally constitutes a security hazard).

# generate a shell-quoted version of $projectVersion
# this is safe to use, and *must only be used*, in an unquoted context.
printf -v projectVersion_q '%q' "$projectVersion"

cat <<EOF
echo "==== Tagging this version ===="
git checkout master
git pull
git tag -a $projectVersion_q -m "version "$projectVersion_q
git push origin $projectVersion_q

echo "==== Cleaning up merged branches ===="
git branch -r --merged | while read -r line; do
  case \$line in
    [*]|master|dmz|develop) continue ;;
    *) branch=\${line#origin/}; git push origin ":\$branch";;
  esac
done

echo "==== Merging release changes into develop and dmz ===="
git checkout develop
git pull
git merge master
git push origin develop
git checkout dmz
git pull
git merge develop
git push origin dmz
git checkout master
EOF

Approach Two: One Big printf

printf -v projectVersion_q '%q' "$projectVersion"

# Generate an array, one element per line, individually quoted as appropriate for each
lines=(
  'echo "==== Tagging this version ===="'
  'git checkout master'
  'git pull'
  "git tag -a $projectVersion_q -m 'version '$projectVersion_q"
  "git push origin $projectVersion_q"
  ''
  'echo "==== Cleaning up merged branches ===="'
  'git branch -r --merged | while read -r line; do'
  '  case $line in'
  '    [*]|master|dmz|develop) continue ;;'
  '    *) branch=${line#origin/}; git push origin ":$branch";;'
  '  esac'
  'done'
  ''
  'echo "==== Merging release changes into develop and dmz ===="'
  'git checkout develop'
  'git pull'
  'git merge master'
  'git push origin develop'
  'git checkout dmz'
  'git pull'
  'git merge develop'
  'git push origin dmz'
  'git checkout master'
)
printf '%s\n' "${lines[@]}"
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Yeah, from the outside it looks like a hazard, but the generated code is being run by me, it's not being run automatically, and the variable is also being set by me. – wheeler Oct 26 '17 at 00:18
  • Not being set by something like a git tag that someone other than you could potentially create? (Or is this a project in which you're the only person with commit access?) – Charles Duffy Oct 26 '17 at 00:19
  • I and one other person on my team, who could also be running this if he runs the build instead. – wheeler Oct 26 '17 at 00:23
  • Gotcha. I'm used to teams-of-hundreds, and needing to worry about minimizing the damage someone who's stolen a developer's credentials can do. (If you're signing commits, maybe an attacker can't add a new commit without a hardware private-key token, but maybe they *can* create an arbitrarily-named tag pointing to an existing commit... y'know, that kind of paranoia). – Charles Duffy Oct 26 '17 at 00:27
  • Quick meta question: turns out the the reason the double quote madness was happening was because of my carelessness in forgetting a closing double quote in an earlier line. Do I state that and answer my own question (I would definitely give props to your well-crafted answer) or do I just mark this one as accepted and note my findings in a comment? Or something else? – wheeler Oct 26 '17 at 00:30
  • Your call, no objection here either way. – Charles Duffy Oct 26 '17 at 00:31
0

Okay, in my carelessness, it turns out in a line (that is not shown) before the first line in the example did not have a closing quote. I closed it and everything worked as expected.

With that said, this answer is worth a read as it's a way better way of doing what I'm doing.

wheeler
  • 2,823
  • 3
  • 27
  • 43
0

If the first echo is a groovy echo (i.e. not running in the shell), then you just need to do triple double quotes to make this work:

echo """ print a whole bunch of multiline stuff in here """

Rob Hales
  • 5,123
  • 1
  • 21
  • 33