0

I have a java app used to automate deployment of files. I would like to create a shell script to make the deployment from svn easier.

Currently, my script checks out the files, gets the filenames, asks for some deployment info then passes the file names to the Jar.

Usually, I can pass multiple files after the -f option, and the Jar collects each file and if the file exists, adds them into an array for processing. If the filename contains a space, I add quotation marks around the filename.

This is the input that usually works with my Jar:

java -jar deploy.jar -f 'Cancellation V2.xsl' 'Final Price.xsl' -e test

My shell script takes the file name from the end of the SVN url, adds quotation marks around the name(s) and passes to the Jar. I get this error:

'Final Price.xsl' 'Cancellation V2.xsl'  not found!
 Exception in thread "main" com.DeployException: 
     at com.ConsoleUtils.parseCommandLineArgs(ConsoleUtils.java:42)
     at com.Main.main(Main.java:23)

This Exception is thrown when the Java app uses if(file.exists) on each filename before adding it to the array. This log shows that both file names are being passed as a single string, even though the shell script has formatted them correctly.

Here is my full Shell script:

#!/bin/bash
now=`date +%Y%m%d%H%M`
mkdir 'deployment-'$now
cd 'deployment-'$now

echo 'Please type tag name to deploy from:'
   read tag

#svn export svn://------[removed]------
#This checks out a list of SVN urls to deploy.

j=1
for i in `cat deploy-filelist`
do
   filename=$i
   $j = $j++
   svn export $filename
   echo $filename | rev | cut -d/ -f1 | rev >> files.txt
done

echo 'loaded' $j 'files'

#svn export svn://------[removed]------
#This checks out the Jar used to deploy the files

files=''
for f in `cat files.txt`
do
   filename="${f/\%20/ }"
   files+="'$filename' "
done

echo $files >> files.txt

echo "Please type environment to deploy to [devl | test | etc]:"
  read env

java -jar deploy.jar -f "$files" -e $env

Can anyone give me some advice on handling the file names? Why is the variable read as one String when the same format is usually ok?

Other comments aprreciated.

Thanks for reading

Jacobo de Vera
  • 1,863
  • 1
  • 16
  • 20
a.hrdie
  • 716
  • 2
  • 14
  • 35
  • Did you try removing double quotes from $files that is passed to java program? Like: `java -jar deploy.jar -f $files -e $env` – Mubin Apr 25 '14 at 09:43
  • Yes, this was my first attempt to fix it. This causes the space to be read as a character. I get an error "cannot find 'Cancellation" (with the quotation mark) – a.hrdie Apr 25 '14 at 09:46
  • Enclose the file names in double-quotes rather than single quotes – Mubin Apr 25 '14 at 09:47
  • 1
    With spaces in file names, working with shell scripts becomes nightmareish quickly. Shells tend to split things at spaces. – Jacobo de Vera Apr 25 '14 at 09:54
  • Enclosing each filename in double quotes, and removing the quotes around $files treats the space in the file name as a split. Get the error - cannot find "Final – a.hrdie Apr 25 '14 at 10:02
  • see my answer, it explains how to read lines with spaces from a file. – Jacobo de Vera Apr 25 '14 at 10:09
  • 1
    As an aside, `$j = $j++` contains multiple syntax errors. (1) No `$` in front of assigned variable. (2) No whitespace around equals sign. (3) There is no `$j++` (but you can use various Bash extensions like `let` etc). – tripleee Apr 25 '14 at 10:16
  • 1
    True, you could also do `(( j++ ))` – Jacobo de Vera Apr 25 '14 at 10:18
  • Thanks for the advice, much appreciated. Had wondered why my counter wasn't working! – a.hrdie Apr 25 '14 at 10:32

4 Answers4

2

The last line of the code shows "$files", which means that the following is performed to the strings:

"'filename1' 'filename2'"

This is therefore read as one string by the JVM and passed through as a single argument.

To fix this, remove the quotes around $files as Mubin also suggests.

Further tip, try echoing your output at the different stages of the Shell so that you can see what's happening to the variables throughout the program. Helps massively when debugging Shell.

Rich Pickering
  • 237
  • 2
  • 6
2

There really isn't a good way in "classic" Bourne shell to quote stuff with internal quotes and interpolate it later. Since you are using Bash on the shebang line, the simple and straightforward workaround is to have your list of file names in an array instead.

IFS=$'\n' urls=($(cat deploy-filelist))
for u in "${urls[@]}"; do
    svn export "$u"
done
echo "Exported ${#urls[@]} files"

IFS=$'\n' basenames=($(sed 's%.*/%%;s/%20/ /g' deploy-filelist))

Notice how this does away with some of the rather tortured shell script you had around writing and rewriting the files.txt file, which I assume you don't need for anything else (creating it as a byproduct is obviously trivial if you need it for other reasons).

(I renamed the arrays to urls and basenames to keep them self-documenting and unique. The first one apparently contains the SVN repo URLs while the second contains just the nondirectory part, with any %20 normalized back to a space.)

Now you can interpolate the array, with quoting preserved, like

java -jar deploy.jar -f "${basenames[@]}" -e $env

(This still looks slightly funny to me. Is the -f option a boolean and you supply two file names, or is -f an option which requires a file name argument, and you should have two of them?)

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • -f accepts any amount of Strings as a list of filenames until the next String is an option. – a.hrdie Apr 25 '14 at 10:30
  • Thanks for your answer, I am currently implementing the array. Would you recommend another way of reading the file names? – a.hrdie Apr 25 '14 at 10:37
  • The original answer didn't show how to do that but I have made a few edits just now. – tripleee Apr 25 '14 at 10:37
  • It worked! and the code looks much nicer. Thanks very much for your help :) – a.hrdie Apr 25 '14 at 10:45
  • 1
    See also http://stackoverflow.com/questions/11393817/bash-read-lines-in-file-into-an-array for some variations on how to read lines into an array in Bash. Notice in particular `read -a`. – tripleee Apr 25 '14 at 10:55
1

The problem starts here:

`for f in `cat files.txt`

You might assume that this will give you one f per line, but it won't, it will give you one f per word. So if your files have spaces, it will split them.

You can replace it with:

cat files.txt | while read -r
do
    f="$REPLY"
    # rest of your code here
done

Then still drop the double quotes at the jar invocation line.

The read command will read each line from stdin, where we are piping the list of files, one per line, and store it in the $REPLY variable. It does not split words like the for does.

Jacobo de Vera
  • 1,863
  • 1
  • 16
  • 20
  • 1
    You need to use `while read; ...; done < files.txt` -- using `cat` and a pipe will force the while loop into a subshell, and any variables you set in a subshell will vanish when the subshell exits. – glenn jackman Apr 25 '14 at 10:36
  • Thanks a lot for your answers, this nearly worked but implementing the Array looked like the logical solution. Appreciate the advice. – a.hrdie Apr 25 '14 at 10:48
1

a quick rewrite, incorporating much of the given advice, adding a little of my own:

#!/bin/bash
dir=deployment-$(date +%Y%m%d%H%M)
mkdir "$dir" || { echo "could not mkdir $dir"; exit 1; }
cd "$dir"

read -p 'Please type tag name to deploy from: ' tag

#svn export svn://------[removed]------
#This checks out a list of SVN urls to deploy.

j=1
files=()
while read -r filename; do
    if [[ -f "$filename" ]]; then
        ((j++))
        svn export "$filename"
        files+=( "$(basename "$filename")" )
    else
        echo "warning: no such file: $filename"
    fi
done < deploy-filelist

echo "loaded $j files"
echo ${files[@]}" >> files.txt

#svn export svn://------[removed]------
#This checks out the Jar used to deploy the files

while :; do
    read -p "Please type environment to deploy to [devl | test | etc]: " env
    case $env in
        devl|test|etc) break ;;
    esac
done

java -jar deploy.jar -f "${files[@]}" -e $env
glenn jackman
  • 238,783
  • 38
  • 220
  • 352