Получение нескольких аргументов для одной опции с помощью getopts в Bash
Мне нужна помощь с getopts
.
Я создал Bash script, который выглядит следующим образом:
$foo.sh -i env -d каталог -s подкаталог -f файл
Он корректно работает при обработке одного аргумента из каждого флага. Но когда я вызываю несколько аргументов из каждого флага, я не уверен, как вывести информацию из нескольких переменных из переменных в getopts
.
while getopts ":i:d:s:f:" opt
do
case $opt in
i ) initial=$OPTARG;;
d ) dir=$OPTARG;;
s ) sub=$OPTARG;;
f ) files=$OPTARG;;
esac
done
После захвата параметров я хочу построить структуры каталогов из переменных
foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
Тогда структура каталогов будет
/test/directory/subdirectory/file1
/test/directory/subdirectory/file2
/test/directory/subdirectory/file3
/test/directory/subdirectory2/file1
/test/directory/subdirectory2/file2
/test/directory/subdirectory2/file3
Любые идеи?
Ответы
Ответ 1
Вы можете использовать одну и ту же опцию несколько раз и добавить все значения в массив.
В самом конкретном исходном вопросе решение Ryan mkdir -p
, очевидно, является лучшим.
Однако, для более общего вопроса получения нескольких значений из той же опции с помощью getopts, здесь это:
#!/bin/bash
while getopts "m:" opt; do
case $opt in
m) multi+=("$OPTARG");;
#...
esac
done
shift $((OPTIND -1))
echo "The first value of the array 'multi' is '$multi'"
echo "The whole list of values is '${multi[@]}'"
echo "Or:"
for val in "${multi[@]}"; do
echo " - $val"
done
Вывод будет:
$ /tmp/t
The first value of the array 'multi' is ''
The whole list of values is ''
Or:
$ /tmp/t -m "one arg with spaces"
The first value of the array 'multi' is 'one arg with spaces'
The whole list of values is 'one arg with spaces'
Or:
- one arg with spaces
$ /tmp/t -m one -m "second argument" -m three
The first value of the array 'multi' is 'one'
The whole list of values is 'one second argument three'
Or:
- one
- second argument
- three
Ответ 2
Я знаю, что этот вопрос старый, но я хотел отбросить этот ответ здесь, если кто-то найдет ответ.
Оболочки вроде BASH поддерживают каталоги рекурсивно, как это уже, поэтому script на самом деле не нужен. Например, оригинальный плакат хотел что-то вроде:
$ foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
/test/directory/subdirectory/file1
/test/directory/subdirectory/file2
/test/directory/subdirectory/file3
/test/directory/subdirectory2/file1
/test/directory/subdirectory2/file2
/test/directory/subdirectory2/file3
Это легко сделать с помощью этой командной строки:
pong:~/tmp
[10] rmclean$ mkdir -pv test/directory/{subdirectory,subdirectory2}/{file1,file2,file3}
mkdir: created directory ‘test’
mkdir: created directory ‘test/directory’
mkdir: created directory ‘test/directory/subdirectory’
mkdir: created directory ‘test/directory/subdirectory/file1’
mkdir: created directory ‘test/directory/subdirectory/file2’
mkdir: created directory ‘test/directory/subdirectory/file3’
mkdir: created directory ‘test/directory/subdirectory2’
mkdir: created directory ‘test/directory/subdirectory2/file1’
mkdir: created directory ‘test/directory/subdirectory2/file2’
mkdir: created directory ‘test/directory/subdirectory2/file3’
Или даже немного короче:
pong:~/tmp
[12] rmclean$ mkdir -pv test/directory/{subdirectory,subdirectory2}/file{1,2,3}
mkdir: created directory ‘test’
mkdir: created directory ‘test/directory’
mkdir: created directory ‘test/directory/subdirectory’
mkdir: created directory ‘test/directory/subdirectory/file1’
mkdir: created directory ‘test/directory/subdirectory/file2’
mkdir: created directory ‘test/directory/subdirectory/file3’
mkdir: created directory ‘test/directory/subdirectory2’
mkdir: created directory ‘test/directory/subdirectory2/file1’
mkdir: created directory ‘test/directory/subdirectory2/file2’
mkdir: created directory ‘test/directory/subdirectory2/file3’
Или короче, с большим соответствием:
pong:~/tmp
[14] rmclean$ mkdir -pv test/directory/subdirectory{1,2}/file{1,2,3}
mkdir: created directory ‘test’
mkdir: created directory ‘test/directory’
mkdir: created directory ‘test/directory/subdirectory1’
mkdir: created directory ‘test/directory/subdirectory1/file1’
mkdir: created directory ‘test/directory/subdirectory1/file2’
mkdir: created directory ‘test/directory/subdirectory1/file3’
mkdir: created directory ‘test/directory/subdirectory2’
mkdir: created directory ‘test/directory/subdirectory2/file1’
mkdir: created directory ‘test/directory/subdirectory2/file2’
mkdir: created directory ‘test/directory/subdirectory2/file3’
Или, наконец, используя последовательности:
pong:~/tmp
[16] rmclean$ mkdir -pv test/directory/subdirectory{1..2}/file{1..3}
mkdir: created directory ‘test’
mkdir: created directory ‘test/directory’
mkdir: created directory ‘test/directory/subdirectory1’
mkdir: created directory ‘test/directory/subdirectory1/file1’
mkdir: created directory ‘test/directory/subdirectory1/file2’
mkdir: created directory ‘test/directory/subdirectory1/file3’
mkdir: created directory ‘test/directory/subdirectory2’
mkdir: created directory ‘test/directory/subdirectory2/file1’
mkdir: created directory ‘test/directory/subdirectory2/file2’
mkdir: created directory ‘test/directory/subdirectory2/file3’
Ответ 3
Параметры getopts могут принимать только нуль или один аргумент. Возможно, вы захотите изменить свой интерфейс, чтобы удалить параметр -f, и просто перебрать оставшиеся аргументы без параметров
usage: foo.sh -i end -d dir -s subdir file [...]
Итак,
while getopts ":i:d:s:" opt; do
case "$opt" in
i) initial=$OPTARG ;;
d) dir=$OPTARG ;;
s) sub=$OPTARG ;;
esac
done
shift $(( OPTIND - 1 ))
path="/$initial/$dir/$sub"
mkdir -p "$path"
for file in "[email protected]"; do
touch "$path/$file"
done
Ответ 4
Я исправил ту же проблему, что и у вас, вот так:
Вместо:
foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
Сделай это:
foo.sh -i test -d directory -s "subdirectory subdirectory2" -f "file1 file2 file3"
С разделителем пространства вы можете просто пройти через него с помощью основного цикла. Вот код:
while getopts ":i:d:s:f:" opt
do
case $opt in
i ) initial=$OPTARG;;
d ) dir=$OPTARG;;
s ) sub=$OPTARG;;
f ) files=$OPTARG;;
esac
done
for subdir in $sub;do
for file in $files;do
echo $subdir/$file
done
done
Вот пример вывода:
$ ./getopts.sh -s "testdir1 testdir2" -f "file1 file2 file3"
testdir1/file1
testdir1/file2
testdir1/file3
testdir2/file1
testdir2/file2
testdir2/file3
Ответ 5
Фактически существует способ получить несколько аргументов с помощью getopts
, но для этого требуется ручное рушительство с переменной getopts
'OPTIND
.
См. следующий script (воспроизведенный ниже): https://gist.github.com/achalddave/290f7fcad89a0d7c3719. Вероятно, это был более простой способ, но это был самый быстрый способ найти.
#!/bin/sh
usage() {
cat << EOF
$0 -a <a1> <a2> <a3> [-b] <b1> [-c]
-a First flag; takes in 3 arguments
-b Second flag; takes in 1 argument
-c Third flag; takes in no arguments
EOF
}
is_flag() {
# Check if $1 is a flag; e.g. "-b"
[[ "$1" =~ -.* ]] && return 0 || return 1
}
# Note:
# For a, we fool getopts into thinking a doesn't take in an argument
# For b, we can just use getopts normal behavior to take in an argument
while getopts "ab:c" opt ; do
case "${opt}" in
a)
# This is the tricky part.
# $OPTIND has the index of the _next_ parameter; so "\$$((OPTIND))"
# will give us, e.g., $2. Use eval to get the value in $2.
eval "a1=\$$((OPTIND))"
eval "a2=\$$((OPTIND+1))"
eval "a3=\$$((OPTIND+2))"
# Note: We need to check that we're still in bounds, and that
# a1,a2,a3 aren't flags. e.g.
# ./getopts-multiple.sh -a 1 2 -b
# should error, and not set a3 to be -b.
if [[ $((OPTIND+2)) > $# ]] || is_flag "$a1" || is_flag "$a2" || is_flag "$a3"
then
usage
echo
echo "-a requires 3 arguments!"
exit
fi
echo "-a has arguments $a1, $a2, $a3"
# "shift" getopts' index
OPTIND=$((OPTIND+3))
;;
b)
# Can get the argument from getopts directly
echo "-b has argument $OPTARG"
;;
c)
# No arguments, life goes on
echo "-c"
;;
esac
done
Ответ 6
Исходный вопрос касается getopts, но есть еще одно решение, которое обеспечивает более гибкую функциональность без getopts (это, возможно, немного более многословно, но обеспечивает гораздо более гибкий интерфейс командной строки). Вот пример:
while [[ $# > 0 ]]
do
key="$1"
case $key in
-f|--foo)
nextArg="$2"
while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
case $nextArg in
bar)
echo "--foo bar found!"
;;
baz)
echo "--foo baz found!"
;;
*)
echo "$key $nextArg found!"
;;
esac
if ! [[ "$2" =~ -.* ]]; then
shift
nextArg="$2"
else
shift
break
fi
done
;;
-b|--bar)
nextArg="$2"
while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
case $nextArg in
foo)
echo "--bar foo found!"
;;
baz)
echo "--bar baz found!"
;;
*)
echo "$key $nextArg found!"
;;
esac
if ! [[ "$2" =~ -.* ]]; then
shift
nextArg="$2"
else
shift
break
fi
done
;;
-z|--baz)
nextArg="$2"
while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
echo "Doing some random task with $key $nextArg"
if ! [[ "$2" =~ -.* ]]; then
shift
nextArg="$2"
else
shift
break
fi
done
;;
*)
echo "Unknown flag $key"
;;
esac
shift
done
В этом примере мы перебираем все параметры командной строки, ища параметры, которые соответствуют принятым флагам командной строки (например, -f или --foo). Как только мы найдем флаг, мы прокручиваем каждый параметр до тех пор, пока не закончим параметры или не встретим другой флаг. Это нарушает нас в нашем внешнем цикле, который обрабатывает только флаги.
При этой настройке следующие команды эквивалентны:
script -f foo bar baz
script -f foo -f bar -f baz
script --foo foo -f bar baz
script --foo foo bar -f baz
Вы также можете анализировать невероятно дезорганизованные наборы параметров, такие как:
script -f baz derp --baz herp -z derp -b foo --foo bar -q llama --bar fight
Чтобы получить результат:
--foo baz found!
-f derp found!
Doing some random task with --baz herp
Doing some random task with -z derp
--bar foo found!
--foo bar found!
Unknown flag -q
Unknown flag llama
--bar fight found!
Ответ 7
Если вы хотите указать любое количество значений для параметра, вы можете использовать простой цикл, чтобы найти их и ввести в массив. Например, позвольте модифицировать пример OP, чтобы разрешить любое количество параметров -s:
unset -v sub
while getopts ":i:d:s:f:" opt
do
case $opt in
i ) initial=$OPTARG;;
d ) dir=$OPTARG;;
s ) sub=("$OPTARG")
until [[ $(eval "echo \${$OPTIND}") =~ ^-.* ]] || [ -z $(eval "echo \${$OPTIND}") ]; do
sub+=($(eval "echo \${$OPTIND}"))
OPTIND=$((OPTIND + 1))
done
;;
f ) files=$OPTARG;;
esac
done
Это принимает первый аргумент ($ OPTARG) и помещает его в массив $sub. Затем он будет продолжать поиск оставшихся параметров до тех пор, пока он не попадет в другой пунктирный параметр ИЛИ больше нет аргументов для оценки. Если он находит больше параметров, которые не являются пунктирным параметром, он добавляет его в массив $sub и набирает переменную $OPTIND.
Итак, в примере OP может быть выполнено следующее:
foo.sh -i test -d directory -s subdirectory1 subdirectory2 -f file1
Если мы добавили эти строки в script, чтобы продемонстрировать:
echo ${sub[@]}
echo ${sub[1]}
echo $files
Вывод будет:
subdirectory1 subdirectory2
subdirectory2
file1
Ответ 8
Поскольку вы не показываете, как вы надеетесь построить свой список
/test/directory/subdirectory/file1
. . .
test/directory/subdirectory2/file3
немного неясно, как действовать, но в основном вам нужно добавлять новые значения в соответствующую переменную, т.е.
case $opt in
d ) dirList="${dirList} $OPTARG" ;;
esac
Обратите внимание, что на первом пропуске dir будет пустым, и вы закончите с пробелом, ведущим от вашего окончательного значения для ${dirList}
. (Если вам действительно нужен код, который не содержит лишних пробелов, спереди или сзади, есть команда, которую я могу вам показать, но это будет трудно понять, и, похоже, вам это не понадобится, но дайте мне знать)
Затем вы можете обернуть переменные списка в циклы for, чтобы испускать все значения, т.е.
for dir in ${dirList} do
for f in ${fileList} ; do
echo $dir/$f
done
done
Наконец, считается хорошей практикой "заманивать" любые неизвестные входы в ваш оператор case, т.е.
case $opt in
i ) initial=$OPTARG;;
d ) dir=$OPTARG;;
s ) sub=$OPTARG;;
f ) files=$OPTARG;;
* )
printf "unknown flag supplied "${OPTARG}\nUsageMessageGoesHere\n"
exit 1
;;
esac
Надеюсь, это поможет.
Ответ 9
#!/bin/bash
myname=$(basename "$0")
# help function
help () { cat <<EOP
$myname: -c cluster [...] -a action [...] -i instance [...]
EOP
}
# parse sub options
get_opts () {
rs='' && rc=0 # return string and return code
while [[ $# -gt 0 ]]; do
shift
[[ "$1" =~ -.* ]] && break || rs="$rs $1" && rc=$((rc + 1))
done
echo "$rs"
}
#parse entire command-line
while [[ $# -gt 0 ]]; do
case $1 in
"-a") ACTS="$(get_opts [email protected])"
;;
"-i") INSTS=$(get_opts [email protected])
;;
"-c") CLUSTERS=$(get_opts [email protected])
;;
"-h") help
;;
?) echo "sorry, I dont do $1"
exit
;;
esac
shift
done