ОШИБКА BASH: синтаксическая ошибка: ожидается операнд (токен ошибки ")

Я новичок в скриптах bash, и у меня возникла проблема с одним из моих скриптов. Я пытаюсь составить список водителей младше 25 лет после чтения их дат рождения в папке, заполненной файлами XML и расчета их возраста. Как только я определил, что им меньше 25 лет, имя файла данных драйвера сохраняется в текстовом файле. Сценарий работает до определенного момента, а затем останавливается. Я получаю ошибку:

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 ")

Вот мой код:

#!/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

Я не уверен, почему он правильно выводит первые 10 имен файлов, а затем останавливается. Есть идеи, почему это может происходить?

3 ответа

Решение

Проблема заключалась в том, что в некоторых файлах было несколько драйверов, поэтому в одну строку импортировалось несколько дат рождения. Мое решение ниже:

#!/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

Вам нужно процитировать расширение $BIRTH_DATE чтобы предотвратить расщепление слова на пробел в значении. (Хорошей практикой является указание всех расширений ваших параметров, если только у вас нет веских причин не делать этого по этой самой причине.)

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

(Исходя из вашего комментария, это, по крайней мере, позволит gdate чтобы дать вам лучшее сообщение об ошибке.)

Реализация лучших практик будет выглядеть примерно так:

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

Если этот сценарий нужно использовать, мои люди, которые не имеют xmlstarlet после установки вы можете сгенерировать шаблон XSLT, а затем использовать xsltproc (которая доступна в готовом виде на современных операционных системах).

То есть, если вы запустите это один раз и объедините его вывод с вашим скриптом:

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

... тогда скрипт можно изменить, чтобы заменить xmlstarlet с:

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

Заметки:

  • Входные XML-файлы читаются с помощью фактического синтаксического анализатора XML.
  • При расширении for file in "$directory"/*.xmlрасширение указано в кавычках, а глобус - нет (что позволяет сценарию работать с каталогами с пробелами, символами глобуса и т. д. в их именах).
  • Выходной файл открывается один раз для цикла, а не один раз для каждой строки вывода (что снижает ненужные издержки на открытие и закрытие файлов).
  • Имена переменных в нижнем регистре используются для соответствия соглашениям POSIX (указывается, что переменные, имеющие значение для операционной системы и оболочки, имеют имена только в верхнем регистре, а набор имен с хотя бы одним символом в нижнем регистре зарезервирован для использование приложения; в то время как рассматриваемые документы относятся к переменным среды, переменные оболочки совместно используют пространство имен, что делает соглашение соответствующим).
Другие вопросы по тегам