Verifique se uma variável contém apenas o que eu quero e nada mais

6

Estou escrevendo um script para criar um software a partir de fontes e há uma opção --platforms . Gostaria de permitir que o usuário selecione vários itens, mas não sei como evitar que eles cometerem erros.

Exemplo:

read -p "For what platforms do you wish to build [mac/win/linux32/linux64/all] ? "

if [[ -n "'echo $REPLY | grep 'win\|mac\|linux32\|linux64\|all'" ]] ; then
    echo "ok"
else 
    echo "not ok"
fi

Se o usuário responder linux32 , deve estar OK (e é)

Se o usuário responder linux32,mac , deve estar OK (e é)

Se o usuário responder lulz , ele NÃO deve estar OK (e não é)

Se o usuário responder linux32,lulz , NÃO deve ser OK (e é, esse é o meu problema)

Eu queria saber se você sabia como permitir que o usuário insira o que quiser separado por vírgula, mas apenas se for uma das opções oferecidas pelo script, então, neste caso, linux32 linux64 mac win all .

Talvez com case haja uma maneira de permitir várias entradas ou talvez adicionar elif $REPLY contains anything else than what we want . Outra ideia, poderia awk ser usado? Eu não consigo descobrir como fazer isso.

    
por MrVaykadji 02.06.2014 / 01:20

6 respostas

1

Uma versão simplificada / melhorada de .com / a / 134014/3730 "> answer :

read -p 'Enter a comma-separated list of platforms to build for [win/mac/linux32/linux64/all]: ' input
IFS=',' read -a options <<< "$input"

shopt -s extglob

for option in "${options[@]}"; do
    case "$option" in
        win|mac|linux@(32|64)|all)
            buildcommand="${buildcommand:+$buildcommand,}$option"
            buildvar=1;;
        *)
            printf 'Invalid option "%s" ignored.\n' "$option" >&2;;
    esac
done

IFS=',' read -a options <<< "$buildcommand"

for option in "${options[@]}"; do
    if [[ $option == 'all' ]]; then
        buildcommand='all'
        break
    fi
done

if (( !buildvar )); then
    echo 'Incorrect input. Default build selected.' >&2
    buildcommand='default'
fi
    
por 02.06.2014 / 15:04
5

read pode dividir a entrada em palavras e armazenar o resultado em uma matriz. Defina a variável IFS para o caractere separador de palavras (ela precisa ser um único caractere, não uma string - se o valor de IFS contiver vários caracteres, então cada caractere será um separador de palavras).

IFS=, read -a platforms

Em seguida, verifique cada elemento da matriz em relação ao conjunto de plataformas suportadas.

for p in "${platforms[@]}"; do
  case "$p" in
    win|mac|linux32|linux64) :;;
    all) platforms=(win mac linux32 linux64);;
    *) printf 1>&2 "Unsupported platform: %s\n" "$p"; return 2;;
  esac
done

Você também pode comparar o conjunto de plataformas de uma só vez. Isso é mais conveniente se você não quiser codificar o conjunto de plataformas suportadas no código de verificação¹.

supported_platforms=(win mac linux32 linux64)
IFS=, read -a platforms
bad_platform_names=($(comm -23 <(printf '%s\n' all "${platforms[@]}" | sort -u) \
                               <(printf '%s\n' "${supported_platforms[@]}" | sort -u)))
if [[ ${#bad_platform_names[@]} -ne 0 ]]; then
  printf "Unsupported platform: %s\n" "${bad_platform_names[@]}"
  exit 1
fi
if printf '%s\n' "${platforms[@]}" | grep -qx all; then
  platforms=("${supported_platforms[@]}")
fi

Uma abordagem diferente seria solicitar plataformas uma por vez usando o select incorporado.

¹ Claro que você pode fazer isso em pura festança se preferir.

    
por 02.06.2014 / 01:46
4

Tente isso!

buildvar=0
read -p "For what platforms do you wish to build [mac/win/linux32/linux64/all] ? " input
IFS=',' read -a options <<< "$input"

for option in "${options[@]}"
 do
  case "$option" in
    linux32)
        buildcommand="linux32" && buildvar=1
        ;;
    linux64)
        [ $buildvar == 1 ] && buildcommand="$buildcommand",linux64 || buildcommand="linux64" && buildvar=1
        ;;  
    mac)
        [ $buildvar == 1 ] && buildcommand="$buildcommand",mac || buildcommand="mac" && buildvar=1
        ;;  
    win)
        [ $buildvar == 1 ] && buildcommand="$buildcommand",win || buildcommand="win" && buildvar=1
        ;;
    all)
        buildcommand="all" && buildvar=1
        ;;
    *) 
        echo "'$option' was ignored." 
        ;;  
        esac
    done

[ $buildvar == "0" ] && echo "Incorrect input. Default build selected." && buildcommand="default"

echo "=> $buildcommand"

Isso solicitará uma lista de opções separadas por vírgulas. Em seguida, ele dividirá essa lista em uma matriz e iterará os elementos verificando cada elemento separadamente e, em seguida, combinará todos os elementos "bons" em uma única variável.

Espero que isso ajude!

    
por 02.06.2014 / 01:52
2

Você pode ler uma única linha de entrada com sed e converter o delimitador em novas linhas como:

% sed 'y/,/\n/;q' /dev/tty
> this,is,a,single,line
##OUTPUT
this
is
a
single
line

Como sed grava o resultado como stdout como um arquivo de texto, seguir isso com e x plicit grep é fácil e ocorre em um único fluxo. E, de fato, você pode usar sed como um smart tee se usar seu w rite na função de arquivo; e, se você w rite para arquivar os dispositivos do descritor, poderá dividir sua saída com base nas regras definidas.

Uma função que solicita a entrada e a saída apenas de uma lista delimitada por nova linha de argumentos aceitáveis para saída stdout e errônea para stderr pode ser semelhante a:

_accept_prompt() (
   . /dev/fd/0
   IFS=${delim-,}
   _prompt "$@" >&2
   { _read_split "$@" |
        err=all _grep_ok "$@" |
        sed '1{$d}' >&2
   } 3>&1 | _grep_ok "$@"
) <<\HELPERS

_prompt() {
    cat ; printf ' : '
} <<-PROMPT
    Choose from : $(printf "'%s' " "$@")
    Enter a '$IFS'-delimited selection below...
PROMPT

_read_split() {
    y="y/${IFS}/\n/"
    sed -ne "H;x;s/^/Invalid input IGNORED:/;${y};p;x" \
        -ne "/all/s/.*/$*/;${y};w /dev/fd/3" -ne q
} </dev/tty

_grep_ok() {
    grep -${err+v}xF "$(printf '%s\n' "$@" $err)"
}
HELPERS

Eu divido isso em esperançosamente funções auxiliares mais descritivamente nomeadas no lugar dos comentários e as colei na função principal. Então o fluxo acontece nas primeiras linhas. Eu esperava deixar isso mais claro.

_read_split gera dois fluxos - >&1 e >&3 . _grep_ok pega o primeiro com $err definido e grava em >&2 todas as linhas contidas em sua entrada que não estão entre os parâmetros posicionais de _accept_prompt .

_grep_ok também concorrentemente seleciona o segundo fluxo - >&3 e grava em suas linhas >&1 stdout all em sua entrada que estão entre _accept_prompt ' s parâmetros posicionais.

Executando:

% _accept_prompt this is the list of acceptable parameters
###PROMPT
    Choose from : 'this' 'is' 'the' 'list' 'of' 'acceptable' 'parameters'
    Enter a ','-delimited selection below...
###INPUT
 : all,invalid
###STDOUT
this
is
the
list
of
acceptable
parameters
###STDERR
Invalid input IGNORED:
invalid

Você pode alterar o delimitador de vírgula , padrão na invocação como:

delim=? _accept_prompt $args
    
por 02.06.2014 / 12:54
1

Suponho que você já tenha alguma forma de obter as variáveis da entrada, desde que você mencionou uma opção --platforms. Você pode usar expressões regulares para corresponder às condições válidas.

regex='^(mac|win|linux(32|64)|all)(,(mac|win|linux(32|64)|all))*$'
if [[ $1 =~ $regex ]]; then 
  echo "ok"
else 
  echo "not ok"
fi

Isso deve satisfazer todos os requisitos:

$ ./stack.sh linux32
ok
$ ./stack.sh linux32,mac
ok
$ ./stack.sh lulz
not ok
$ ./stack.sh linux32,lulz
not ok

Concedido, isso não impede que as pessoas usem "all, all, all, all", mas, pelo menos, podem ser reduzidas quando as coisas são executadas mais tarde.

Os benefícios deste método incluem poder fazer um loop até que o usuário acerte, se você estiver usando o comando read que você tem no primeiro post. Desvantagens incluem ser um pouco confuso para apenas olhar para.

regex='^(mac|win|linux(32|64)|all)(,(mac|win|linux(32|64)|all))*$'
while ! [[ $REPLY =~ $regex ]]; do
  read -p "For what platforms do you wish to build [mac/win/linux32/linux64/all] ? "
done
    
por 03.06.2014 / 02:50
0

O método alternativo sugerido por Gilles faz com que os usuários insiram o mínimo possível, solicitando as opções.

available_platforms="mac win linux32 linux64"

positive() {
  printf "%s (Answer y for yes or anything else for no) " "$1"
  read answer
  if [ "$answer" != "y" ] && [ "$answer" != "Y" ]; then
    return 1
  fi
}

if positive "Do you want to build for all platforms [$available_platforms]?"; then
  build_platforms="$available_platforms"
else
  for platform in $available_platforms; do
    if positive "Do you want to build for [$platform]?"; then
      build_platforms="${build_platforms}${platform} "
    fi
  done
fi

printf "build_platforms: %s\n" "$build_platforms"
    
por 02.06.2014 / 20:32

Tags