AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 459367
Accepted
Florian Brucker
Florian Brucker
Asked: 2018-07-31 05:52:28 +0800 CST2018-07-31 05:52:28 +0800 CST 2018-07-31 05:52:28 +0800 CST

Usando variáveis ​​de shell para opções de comando

  • 772

Em um script Bash, estou tentando armazenar as opções que estou usando rsyncem uma variável separada. Isso funciona bem para opções simples (como --recursive), mas estou tendo problemas com --exclude='.*':

$ find source
source
source/.bar
source/foo

$ rsync -rnv --exclude='.*' source/ dest
sending incremental file list
foo

sent 57 bytes  received 19 bytes  152.00 bytes/sec
total size is 0  speedup is 0.00 (DRY RUN)

$ RSYNC_OPTIONS="-rnv --exclude='.*'"

$ rsync $RSYNC_OPTIONS source/ dest
sending incremental file list
.bar
foo

sent 78 bytes  received 22 bytes  200.00 bytes/sec
total size is 0  speedup is 0.00 (DRY RUN)

Como você pode ver, passar --exclude='.*'para rsync"manualmente" funciona bem ( .barnão é copiado), não funciona quando as opções são armazenadas em uma variável primeiro.

Suponho que isso esteja relacionado às aspas ou ao curinga (ou ambos), mas não consegui descobrir exatamente o que está errado.

bash variable
  • 3 3 respostas
  • 26257 Views

3 respostas

  • Voted
  1. Best Answer
    Kusalananda
    2018-07-31T05:55:22+08:002018-07-31T05:55:22+08:00

    Em geral, é uma má ideia rebaixar uma lista de itens separados em uma única string, seja uma lista de opções de linha de comando ou uma lista de nomes de caminho.

    Usando uma matriz em vez disso:

    rsync_options=( -rnv --exclude='.*' )
    

    ou

    rsync_options=( -r -n -v --exclude='.*' )
    

    e depois...

    rsync "${rsync_options[@]}" source/ target
    

    Desta forma, a cotação das opções individuais é mantida (desde que você faça aspas duplas na expansão de ${rsync_options[@]}). Ele também permite que você manipule facilmente as entradas individuais da matriz, se você precisar fazer isso, antes de chamar rsync.

    Em qualquer shell POSIX, pode-se usar a lista de parâmetros posicionais para isso:

    set -- -rnv --exclude='.*'
    
    rsync "$@" source/ target
    

    Novamente, aspas duplas na expansão de $@são críticas aqui.

    Relacionado tangencialmente:

    • Como podemos executar um comando armazenado em uma variável?

    O problema é que quando você coloca os dois conjuntos de opções em uma string, as aspas simples --excludedo valor da opção se tornam parte desse valor. Por isso,

    RSYNC_OPTIONS='-rnv --exclude=.*'
    

    teria funcionado¹... mas é melhor (como mais seguro) usar um array ou os parâmetros posicionais com entradas entre aspas individualmente. Fazer isso também permitiria que você usasse coisas com espaços, se necessário, e evita que o shell execute a geração de nome de arquivo (globbing) nas opções.


    ¹ desde que $IFSnão seja modificado e que não haja nenhum arquivo cujo nome comece --exclude=.no diretório atual e que as opções nullglobou failglobshell não estejam definidas.

    • 66
  2. Florian Brucker
    2018-07-31T23:36:38+08:002018-07-31T23:36:38+08:00

    @Kusalananda já explicou o problema básico e como resolvê-lo, e a entrada Bash FAQ vinculada por @glenn jackmann também fornece muitas informações úteis. Aqui está uma explicação detalhada do que está acontecendo no meu problema com base nesses recursos.

    Usaremos um pequeno script que imprime cada um de seus argumentos em uma linha separada para ilustrar as coisas ( argtest.bash):

    #!/bin/bash
    
    for var in "$@"
    do
        echo "$var"
    done
    

    Opções de passagem "manualmente":

    $ ./argtest.bash -rnv --exclude='.*'
    -rnv
    --exclude=.*
    

    Como esperado, as partes -rnve --exclude='.*'são divididas em dois argumentos, pois são separados por espaços em branco sem aspas (isso é chamado de divisão de palavras ).

    Observe também que as aspas .*foram removidas: as aspas simples dizem ao shell para passar seu conteúdo sem interpretação especial , mas as aspas em si não são passadas para o comando .

    Se agora armazenarmos as opções em uma variável como uma string (em vez de usar uma matriz), as aspas não serão removidas :

    $ OPTS="--exclude='.*'"
    
    $ ./argtest.bash $OPTS
    --exclude='.*'
    

    Isso ocorre por dois motivos: as aspas duplas usadas na definição $OPTSimpedem um tratamento especial das aspas simples, de modo que as últimas fazem parte do valor:

    $ echo $OPTS
    --exclude='.*'
    

    Quando agora usamos $OPTScomo argumento para um comando, as aspas são processadas antes da expansão do parâmetro , de modo que as aspas $OPTSocorrem "tarde demais".

    Isso significa que (no meu problema original) rsyncusa o padrão de exclusão '.*'(com aspas!) em vez do padrão .*- ele exclui arquivos cujo nome começa com aspas simples seguido por um ponto e termina com aspas simples. Obviamente não era isso que se pretendia.

    Uma solução alternativa seria omitir as aspas duplas ao definir $OPTS:

    $ OPTS2=--exclude='.*'
    
    $ ./argtest.bash $OPTS2
    --exclude=.*
    

    No entanto, é uma boa prática sempre citar atribuições de variáveis ​​devido a diferenças sutis em casos mais complexos.

    Como @Kusalananda observou, não citar .*também teria funcionado. Eu adicionei as aspas para evitar a expansão do padrão , mas isso não era estritamente necessário neste caso especial :

    $ ./argtest.bash --exclude=.*
    --exclude=.*
    

    Acontece que o Bash executa a expansão do padrão, mas o padrão --exclude=.*não corresponde a nenhum arquivo, então o padrão é passado para o comando. Comparar:

    $ touch some_file
    
    $ ./argtest.bash some_*
    some_file
    
    $ ./argtest.bash does_not_exit_*
    does_not_exit_*
    

    No entanto, não citar o padrão é perigoso, porque se (por qualquer motivo) houver um arquivo correspondente --exclude=.*, o padrão será expandido:

    $ touch -- --exclude=.special-filenames-happen
    
    $ ./argtest.bash --exclude=.*
    --exclude=.special-filenames-happen
    

    Finalmente, vamos ver por que usar um array evita meu problema de citação (além das outras vantagens de usar arrays para armazenar argumentos de comando).

    Ao definir o array, a divisão de palavras e o tratamento de aspas acontecem conforme o esperado:

    $ ARRAY_OPTS=( -rnv --exclude='.*' )
    
    $ echo length of the array: "${#ARRAY_OPTS[@]}"
    length of the array: 2
    
    $ echo first element: "${ARRAY_OPTS[0]}"
    first element: -rnv
    
    $ echo second element: "${ARRAY_OPTS[1]}"
    second element: --exclude=.*
    

    Ao passar as opções para o comando, usamos a sintaxe "${ARRAY[@]}", que expande cada elemento do array em uma palavra separada:

    $ ./argtest.bash "${ARRAY_OPTS[@]}"
    -rnv
    --exclude=.*
    
    • 6
  3. champion-runner
    2019-11-20T04:09:44+08:002019-11-20T04:09:44+08:00

    Quando escrevemos funções e scripts de shell, nos quais os argumentos são passados ​​para serem processados, os argumentos serão passados ​​em variáveis ​​nomeadas numericamente, por exemplo, $1, $2, $3

    Por exemplo :

    bash my_script.sh Hello 42 World

    Dentro my_script.shde , os comandos usarão $1para se referir a Hello, $2to 42e $3forWorld

    A referência da variável, $0, será expandida para o nome do script atual, por exemplomy_script.sh

    Não jogue todo o código com comandos como variáveis.

    Tenha em mente :

    1 Evite usar nomes de variáveis ​​em letras maiúsculas em scripts.

    2 Não use aspas, use $(...) em vez disso, aninha melhor.

    if [ $# -ne 2 ]
    then
        echo "Usage: $(basename $0) DIRECTORY BACKUP_DIRECTORY"
        exit 1
    fi
    
    directory=$1
    backup_directory=$2
    current_date=$(date +%Y-%m-%dT%H-%M-%S)
    backup_file="${backup_directory}/${current_date}.backup"
    
    tar cv "$directory" | openssl des3 -salt | split -b 1024m - "$backup_file"
    
    • 0

relate perguntas

  • Problema estranho ao passar variáveis ​​do arquivo de texto

  • Enquanto a linha lê mantendo os espaços de escape?

  • Como salvar um caminho com ~ em uma variável?

  • ordem de substituição de processos `te` e `bash`

  • Execute um script muito lento até que seja bem-sucedido

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    ssh Não é possível negociar: "nenhuma cifra correspondente encontrada", está rejeitando o cbc

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    Como descarregar o módulo do kernel 'nvidia-drm'?

    • 13 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Wong Jia Hau ssh-add retorna com: "Erro ao conectar ao agente: nenhum arquivo ou diretório" 2018-08-24 23:28:13 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST
  • Martin Hope
    Bagas Sanjaya Por que o Linux usa LF como caractere de nova linha? 2017-12-20 05:48:21 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve