kbdev
kbdev

Reputation: 1315

BASH ERROR: syntax error: operand expected (error token is ")

I am new to bash scripting, and I'm having an issue with one of my scripts. I'm trying to compose a list of Drivers Under 25 after reading their birthdates in from a folder filled with XML files and calculating their ages. Once I have determined they are under 25, the filename of the driver's data is saved to a text file. The script is working up until a certain point and then it stops. The error I'm getting is:

gdate: extra operand ‘+%s’
Try 'gdate --help' for more information.
DriversUnder25.sh: line 24: ( 1471392000 -  )/60/60/24 : syntax error: operand expected (error token is ")/60/60/24 ")

Here is my code:

#!/bin/bash

# define directory to search and current date
DIRECTORY="/*.xml"
CURRENT_DATE=$(date '+%Y%m%d')

# loop over files in a directory
for FILE in $DIRECTORY;
do
  # grab user's birth date from XML file
  BIRTH_DATE=$(sed -n '/Birthdate/{s/.*<Birthdate>//;s/<\/Birthdate.*//;p;}' $FILE)

  # calculate the difference between the current date
  # and the user's birth date (seconds)
  DIFFERENCE=$(( ( $(gdate -ud $CURRENT_DATE +'%s') - $(gdate -ud $BIRTH_DATE +'%s') )/60/60/24 ))

  # calculate the number of years between
  # the current date and the user's birth date
  YEARS=$(($DIFFERENCE / 365))

  # if the user is under 25
  if [ "$YEARS" -le 25 ]; then
    # save file name only
    FILENAME=`basename $FILE`
    # output filename to text file
    echo $FILENAME >> DriversUnder25.txt
  fi
done

I'm not sure why it correctly outputs the first 10 filenames and then stops. Any ideas why this may be happening?

Upvotes: 2

Views: 6233

Answers (3)

kbdev
kbdev

Reputation: 1315

The issue was that there were multiple drivers in some files, thus importing multiple birth dates into the same string. My solution is below:

#!/bin/bash

# define directory to search and current date
DIRECTORY="/*.xml"
CURRENT_DATE=$(date '+%Y%m%d')

# loop over files in a directory
for FILE in $DIRECTORY;
do
  # set flag for output to false initially
  FLAG=false

  # grab user's birth date from XML file
  BIRTH_DATE=$(sed -n '/Birthdate/{s/.*<Birthdate>//;s/<\/Birthdate.*//;p;}' $FILE)

  # loop through birth dates in file (there can be multiple drivers)
  for BIRTHDAY in $BIRTH_DATE;
  do
    # calculate the difference between the current date
    # and the user's birth date (seconds)
    DIFFERENCE=$(( ( $(gdate -ud $CURRENT_DATE +'%s') - $(gdate -ud $BIRTHDAY +'%s') )/60/60/24))

    # calculate the number of years between
    # the current date and the user's birth date
    YEARS=$(($DIFFERENCE / 365))

    # if the user is under 25
    if [ "$YEARS" -le 25 ]; then
      # save file name only
      FILENAME=`basename $FILE`
      # set flag to true (driver is under 25 years of age)
      FLAG=true
    fi
  done

  # if there is a driver under 25 in the file
  if $FLAG == true; then
    # output filename to text file
    echo $FILENAME >> DriversUnder25.txt
  fi
done

Upvotes: 0

Charles Duffy
Charles Duffy

Reputation: 295279

A best-practices implementation would look something like this:

directory=/ # patch as appropriate
current_date_unix=$(date +%s)

for file in "$directory"/*.xml; do
    while IFS= read -r birth_date; do
        birth_date_unix=$(gdate -ud "$birth_date" +'%s')
        difference=$(( ( current_date_unix - birth_date_unix ) / 60 / 60 / 24 ))
        years=$(( difference / 365 ))
        if (( years < 25 )); then
            echo "${file%.*}"
        fi
    done < <(xmlstarlet sel -t -m '//Birthdate' -v . -n <"$file")
done >DriversUnder25.txt

If this script needs to be usable my folks who don't have xmlstarlet installed, you can generate an XSLT template and then use xsltproc (which is available out-of-the-box on modern opertaing systems).

That is to say, if you run this once, and bundle its output with your script:

xmlstarlet sel -C -t -m '//Birthdate' -v . -n  >get-birthdays.xslt

...then the script can be modified to replace xmlstarlet with:

xsltproc get-birthdays.xslt - <"$file"

Notes:

  • The XML input files are being read with an actual XML parser.
  • When expanding for file in "$directory"/*.xml, the expansion is quoted but the glob is not (thus allowing the script to operate on directories with spaces, glob characters, etc. in their names).
  • The output file is being opened once, for the loop, rather than once per line of output (reducing overhead unnecessarily opening and closing files).
  • Lower-case variable names are in use to comply with POSIX conventions (specifying that variables with meaning to the operating system and shell have all-upper-case names, and that the set of names with at least one lower-case character is reserved for application use; while the docs in question are with respect to environment variables, shell variables share a namespace, making the convention relevant).

Upvotes: 1

chepner
chepner

Reputation: 530922

You need to quote the expansion of $BIRTH_DATE to prevent word splitting on the whitespace in the value. (It is good practice to quote all your parameter expansions, unless you have a good reason not to, for this very reason.)

DIFFERENCE=$(( ( $(gdate -ud "$CURRENT_DATE" +'%s') - $(gdate -ud "$BIRTH_DATE" +'%s') )/60/60/24 ))

(Based on your comment, this would probably at least allow gdate to give you a better error message.)

Upvotes: 3

Related Questions