Разделение параметров оболочки Bourne / POSIX - PullRequest
4 голосов
/ 20 января 2011

Предположим, у меня есть какая-то утилита, которая может принимать несколько опций, каждая из которых сопровождается именем файла.Например, я мог бы назвать это как myutil, myutil -o somefile, myutil -p anotherfile, myutil -o somefile -p anotherfile и т. Д .... Я хочу написать сценарий оболочки POSIX-оболочки, который может вызывать myutil с произвольными комбинациями параметров(в зависимости от некоторых внутренних условий скрипта-обёртки, которые не имеют отношения к этому вопросу).

Я подумал сделать что-то вроде:

#!/bin/sh
file1=somefile
file2=anotherfile

if [ somecriteria = true ]; then
  OPT1="-o $file1"
fi

if [ othercriteria = true ]; then
  OPT2="-p $file2"
fi

myutil $OPT1 $OPT2

Это прекрасно работает - до тех пор, покани одно из имен файлов не имеет пробелов: при условии, что оба значения if истинны, myutil получает $ 1 = [-o], $ 2 = [somefile], $ 3 = [-p] и $ 4 = [anotherfile].Однако, если есть пробелы, например, если file1="some file", $ 1 = [-o], $ 2 = [some], $ 3 = [file] и т. Д. Конечно, я хочу получить $ 2 = [some file].

Помещение другого набора кавычек вокруг имени файла в OPT1 и OPT2 не помогает;например, если я изменю его на OPT1="-o \"$file1\"", то получу $ 2 = ["some] и $ 3 = [file"].И вставка кавычек вокруг $ OPT1 и $ OPT2 в вызове myutil также не работает: если я это сделаю, $ 1 = [-o некоторый файл].

Итак, есть ли какой-то трюк длязаставить это работать, или какой-то другой подход, который будет делать то, что я хочу?Я бы хотел, чтобы это соответствовало стандартным функциям оболочки, поэтому без bash-isms или ksh-isms, пожалуйста :) См. this для описания того, что в стандарте.

Ответы [ 7 ]

3 голосов
/ 20 января 2011

Поработав еще, я нашел другой подход, который, кажется, делает то, что я хочу:

#!/bin/sh
file1="some file"
file2=anotherfile

if [ somecriteria = true ]; then
  OPT1="$file1"
fi

if [ othercriteria = true ]; then
  OPT2="$file2"
fi

myutil ${OPT1:+-o "$OPT1"} ${OPT2:+-p "$OPT2"}

Конструкция $ { параметр : + слово } будет заменена на слово , если задан $ { параметр }; если он не установлен, он уходит. Поэтому, если $OPT1 не установлено, ${OPT1:+-o "$OPT1"} исчезает, в частности, оно не превращается в пустую строку в argv. Если для $OPT1 установлено значение some file, вышеприведенное выражение заменяется на -o "some file", а myutil получает $ 1 = [-o], $ 2 = [какой-то файл], как я хочу.

Обратите внимание, что myutil ${OPT1:+-o} "$OPT1" ${OPT2:+-p} "$OPT2" делает , а не делает именно то, что я хочу, потому что если $OPT1 не установлено, -o уходит, но "$OPT1" превращается в пустую строку - $ 1 = [ ], $ 2 = [-p], $ 3 = [еще один файл]

(отредактировано по предложению Денниса)

2 голосов
/ 20 января 2011

Прежде всего, вам придется указать опции в этой строке sh myutil.sh "$OPT1" "$OPT2"

А вот рабочая реализация без особых -измов, которая использует getopts на стороне myutil.sh.

Этот скрипт вызывает myutil.sh:

#!/bin/sh
somecriteria=true
othercriteria=true
file1="some file"
file2="other file"

if [ $somecriteria = true ]; then
  OPT1="-o$file1"
fi

if [ $othercriteria = true ]; then
  OPT2="-p$file2"
fi

sh myutil.sh "$OPT1" "$OPT2"

А вот так может выглядеть myutil.sh:

#!/bin/sh
OPTIND=1
while getopts "o:p:" opt; do
  case "$opt" in
    o)  file1=$OPTARG
        ;;
    p)  file2=$OPTARG
        ;;
  esac
done
shift $((OPTIND-1))

echo 'File 1: "'$file1'"'
echo 'File 2: "'$file2'"'

Как вы можете видеть в выходных данных myutil.sh, пробелы в именах файлов сохраняются:

File 1: "some file"
File 2: "other file"
2 голосов
/ 20 января 2011

Похоже, вы нашли достойное решение POSIX. Однако вы можете использовать set для вызова вашей программы как myutil "$@". Ваше решение становится немного громоздким с ростом числа возможных параметров.

#!/bin/sh
file1=somefile
file2=anotherfile

if [ somecriteria = true ]; then
  set -- "-o" "$file1"
fi

if [ othercriteria = true ]; then
  set -- "$@" "-p" "$file2"
fi

myutil "$@"

Пример

#!/bin/sh

file1="some file"
file2="another file"

# Default to '1' if not overwritten
: ${x:=1}
: ${y:=1}

if [ $x -eq 1 ]; then
  set -- "-o" "$file1"
fi

if [ $y -eq 1 ]; then
  set -- "$@" "-p" "$file2"
fi

printf "[%s]" "$@"
echo

выход

$ x=0 y=0 ./opt.sh
[]
$ x=0 y=1 ./opt.sh
[-p][another file]
$ x=1 y=0 ./opt.sh
[-o][some file]
$ x=1 y=1 ./opt.sh
[-o][some file][-p][another file]
1 голос
/ 21 февраля 2015

Я думаю, что ваше собственное решение с использованием $ {OPT1 + -o "$ OPT1"} является хорошим решением, и я не вижу никаких проблем с этим в этом сценарии использования, но есть другой подход, использующий evalчто никто не упомянул, что еще ближе к вашему исходному коду:

#!/bin/sh
FILE1='some file'
FILE2='another file'

if [ somecriteria = true ]; then
 OPT1="-o '$FILE1'"
fi

if [ othercriteria = true ]; then
  OPT2="-p '$FILE2'"
fi

eval myutil "$OPT1" "$OPT2"

Это будет производить то, что вы хотите.

Но вы должны быть осторожны, если ваши имена файлов содержат одинарные кавычкикак часть буквальной строки имени файла.

Если вы точно знаете, как выглядят ваши имена файлов при написании сценария, просто убедитесь, что литералы кавычек вашего имени файла не выходят за пределы того, что вы используете для обозначения имени файла.

Но это еще важнее, когда вы обрабатываете пользовательский ввод или иным образом получаете информацию из окружения.- например, если $ FILE1 определен как abc'; /tmp/malicious_program ', а затем вы выполняете eval, он разберет строку myutil в:

myutil -o 'abc'; /tmp/malicious_program '' -p 'another file'

.., которая является двумя отдельными командами и может быть массивнойдыра в безопасности, в зависимости от того, как именно выполняется этот скрипт относительно сущности, которая создала / tmp / malware_program и установила $ FILE1.

В тех случаях, если вы хотите ввести зависимость от sed, вы можетесначала сделайте что-то вроде этого:

FILE1=\'`printf %s "$FILE1" | sed "s/'/'\\\\''/g"`\'

.. это создаст красивое экранированное имя, заключенное в одинарные кавычки, и любые экранированные кавычки внутри него также будут экранированы.

Поскольку в Борне /Оболочка POSIX, кроме одной кавычки, не может «вырваться» из строки в одинарных кавычках, поэтому в своем примере я использую одинарные кавычки.Двойная кавычка, избегая вещей таким образом, возможна, но ваша команда sed должна быть намного более сложной, так как есть несколько других символов, которые вам нужно экранировать внутри двойных кавычек (в верхней части моей головы: вместо того, чтобы просто избегать одиночных кавычек, вы избегаете двойных кавычек, обратная косая черта, обратные косые черты, знаки доллара и, возможно, другие вещи, о которых я не думаю).

PS: потому что я нашел такой подход «завершение в оболочку, затем подтверждение позже» полезным в нескольких случаях, впо крайней мере, там, где это было совершенно необходимо, я написал крошечную C-программу (и эквивалентную функцию оболочки, использующую sed), которая оборачивает все свои аргументы в экранирование одинарной кавычки, как описано выше, на случай, если кто-то захочет использовать ее вместо того, чтобы реализовывать вашусобственный: esceval

1 голос
/ 20 января 2011

Зачем отмечать простую кавычку вместо двойной кавычки?

if [ somecriteria = true ]; then
  OPT1="-o '$file1'"
fi

if [ othercriteria = true ]; then
  OPT2="-p '$file2'"
fi
0 голосов
/ 20 января 2011

Использование getopts http://mywiki.wooledge.org/BashFAQ/035

«Оболочка POSIX (и другие) предлагает getopts, который безопасен для использования вместо нее.»

0 голосов
/ 20 января 2011

Возможная реализация ниже, учебник по bash-массивам: здесь .

#!/bin/sh

function myutil {
  local a1=$1; shift;
  local a2=$1; shift;
  local a3=$1; shift;
  local a4=$1; shift;

  echo "a1=$a1,a2=$a2,a3=$a3,a4=$a4"
}

file1="some file"
file2="another file"

OPT1=(-o "$file1")
OPT2=(-p "$file2")

myutil "${OPT1[@]}" "${OPT2[@]}"
...