Как я могу использовать getopts в скрипте, который добавляет строки из файлов в отдельном каталоге в новый файл? - PullRequest
1 голос
/ 01 февраля 2020

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

Это работает:

#!/bin/bash

rm /local/SomePath/multigene.firstline.btab
touch /local/SomePath/multigene.firstline.btab

btabdir=/local/SomePath/test/*
outfile=/local/SomePath/multigene.firstline.btab

for f in $btabdir
do
    head -1 $f >> $outfile
done

Это не работает:

#!/bin/bash

while getopts ":d:o:" opt; do
  case ${opt} in
    d) btabdir=$OPTARG;;
    o) outfile=$OPTARG;;
  esac
done

rm $outfile
touch $outfile

for f in $btabdir
do
    head -1 $f >> $outfile
done

Вот как Я называю скрипт:

bash /local/SomePath/Scripts/btab.besthits.wBp-q_wBm-r.sh -d /local/SomePath/test/* -o /local/SomePath/out.test/multigene.firstline.btab

И вот что я получаю, когда запускаю его:

rm: missing operand
Try 'rm --help' for more information.
touch: missing file operand
Try 'touch --help' for more information.
/local/SomePath/Scripts/btab.besthits.wBp-q_wBm-r.sh: line 23: $outfile: ambiguous redirect

Есть предложения? Я хотел бы иметь возможность использовать getopts, чтобы я мог сделать скрипт более обобщенным c. Спасибо!

Ответы [ 3 ]

1 голос
/ 01 февраля 2020

Вы должны обратить особое внимание на цитирование и глобирование при написании bash сценариев.

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

Если вы, например, выполните cat *.txt, cat получит все файлы .txt в каталоге в качестве аргументов. Это будет то же самое, что и вызов cat afile.txt nextfile.txt (и т. Д.). Cat никогда не увидит звездочку.

В вашем скрипте это означает, что входные данные -d /local/SomePath/test/* будут расширены, что-то вроде /local/SomePath/test/someFile /local/SomePath/test/someOtherFile /test/someThirdFile. Впоследствии getopts принимает только первый файл после -d, как для $btabdir, а -o не обрабатывается при переключении регистра.

Я предлагаю вам начать с цитирования каждой переменной, предпочтительно в стиль "${name}", и вызывать скрипт только с вводом в кавычках. Он также может быть отправлен по пути к каталогу, проверить, что это каталог (test -d), и изменить значение для l oop на for f in "${btabdir}"/*

0 голосов
/ 08 февраля 2020

Я думаю, что правильный ответ здесь - «не делай так». : -)

Причина, по которой ваш текущий скрипт не работает, может быть в том, что подстановочный знак расширяется вашей интерактивной оболочкой, а не вашим скриптом. Попробуйте выполнить команду с echo в начале строки, чтобы понять, что на самом деле происходит. Когда getopts видит второй из сопоставленных файлов в глобусе, он прекращает обработку параметров, поэтому -o никогда не читается, а $outfile остается неустановленным. А поскольку вы не заключаете в кавычки свою переменную в rm $outfile, это как если бы вы запускали rm без параметров. Проверьте разницу в вашей оболочке между rm и rm "".

Кроме того, что происходит с вашим for l oop, если в имени файла есть пробел? Так как у вас есть bash, у вас есть массивы. И массивы намного лучше для обработки списков файлов.

Возможно, вместо этого используйте что-то вроде этого:

#!/bin/bash

# initialize an array
files=()

while getopts :d:o: opt; do
  case "$opt" in
    d)
      if [[ ! -d "$OPTARG" ]]; then
        printf 'ERROR: not a directory: %s\n' "$OPTARG" >&2
        exit 65
      fi
      # add to the array
      files+=( "$OPTARG"/* )
      ;;
    o) outfile="$OPTARG" ;;
    *)
      printf 'ERROR: unknown option: %s\n' "$opt" >&2
      exit 64
      ;;
  esac
done

if ! rm -f "$outfile" && touch "$outfile"; then
  printf 'ERROR: cannot create %s\n' "$outfile" >&2
  exit 73
fi

for f in "${files[@]}"; do
  read -r < "$f"
  printf '%s\n' "$REPLY"
done > "$outfile"

Вот некоторые основные моменты изменений ....

  • Мы, конечно, используем массивы , Массив ${files[@]} будет содержать один файл на запись без использования пробелов, поэтому при правильном цитировании вы избежите проблем со специальными символами в именах файлов. показать ошибки и выйти, если мы их увидим. (Выходные значения: sysexits .)
  • Вместо использования head мы используем read и одно перенаправление на $outfile. Это позволяет сохранить несколько вилок во внешней программе и несколько вызовов fopen() в ваш выходной файл.

Обратите внимание, что аргумент -d должен быть каталогом , а не шарик. И вы можете указать параметры несколько раз. Несколько параметров -d будут добавлены вместе, но будет использоваться только последний параметр -o.

0 голосов
/ 07 февраля 2020

Это также работает:

head -n1 -q /local/SomePath/test/* >> /local/SomePath/out.test/multigene.firstline.btab
...