Erro de script Bash com strings com caminhos que possuem espaços e curingas

Estou tendo problemas para obter os fundamentos do script Bash. Aqui está o que eu tenho até agora:

#!/bin/bash FILES="/home/john/my directory/*.txt" for f in "${FILES}" do echo "${f}" done 

Tudo que eu quero fazer é listar todos os .txt arquivos em um for loop para que eu possa fazer coisas com eles. Mas o espaço em my directory e o asterisco em *.txt simplesmente não está jogando bem. Tentei usá-lo com e sem aspas duplas, com e sem chaves nos nomes das variáveis e ainda não consigo “imprimir todos os .txt arquivos.

Este é um coisa muito básica, mas ainda estou lutando porque estou cansado e não consigo pensar direito.

O que estou fazendo de errado?

Consegui me inscrever com sucesso o script acima se meus ARQUIVOS não tiverem um espaço ou um asterisco … Tive que experimentar com ou sem o uso de aspas duplas e colchetes para fazê-lo funcionar. Mas no momento em que tenho espaços e um asterisco, isso bagunça tudo.

Resposta

Entre aspas, o * não se expandirá para uma lista de arquivos. Para usar esse curinga com sucesso, ele deve estar fora das aspas.

Mesmo se o curinga se expandisse, a expressão "${FILES}" resultaria em uma única string, não uma lista de arquivos.

Uma abordagem que funcionaria seria:

#!/bin/bash DIR="/home/john/my directory/" for f in "$DIR"/*.txt do echo "${f}" done 

Acima, os nomes dos arquivos com espaços ou outra dificuldade os caracteres serão tratados corretamente.

Uma abordagem mais avançada poderia usar matrizes bash:

#!/bin/bash FILES=("/home/john/my directory/"*.txt) for f in "${FILES[@]}" do echo "${f}" done 

Neste caso, FILES é uma matriz de nomes de arquivo. Os parênteses ao redor da definição a tornam uma matriz. Observe que * está fora das aspas. A construção "${FILES[@]}" é um caso especial: ela se expandirá para uma lista de strings onde cada string é um dos nomes de arquivo. Nomes de arquivos com espaços ou outros caracteres difíceis serão tratados corretamente.

Comentários

  • ótimo, funcionou
  • É ‘ É importante notar que se você ‘ repassando caminhos como este pelas funções, você precisa garantir que cita a variável por conta própria em vez de concatená-lo como parte de uma string maior: for f in "$DIR"/*.txt = fine for f in "$DIR/*.txt" = quebras

Resposta

Embora usar arrays como mostrado por John1024 faça muito mais sentido, aqui, você também pode usar o operador split + glob (deixando uma variável escalar sem aspas).

Como você deseja apenas a parte global desse operador, é necessário desativar a parte divisão :

#! /bin/sh - # that also works in any sh, so you don"t even need to have or use bash file_pattern="/home/john/my directory/*.txt" # all uppercase variables should be reserved for environment variables IFS="" # disable splitting for f in $file_pattern # here we"re not quoting the variable so # we"re invoking the split+glob operator. do printf "%s\n" "$f" # avoid the non-reliable, non-portable "echo" done 

Resposta

O que você pode fazer é deixar apenas os caracteres curinga fora das aspas.
Algo como:
para a em “arquivos com espaços” * “. txt “
do
processing
done
Se os próprios curingas se expandirem para espaços, você não precisará usar uma abordagem de arquivo por linha, como usar ls -l para gerar a lista de arquivos e use bash read para obter cada arquivo.

Resposta

Solução baseada em uma única linha, (para executar no Terminal):
/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; }; done; unset f;

para o seu caso / OP “, altere "./" para "/home/john/my directory/"

Para usar em um arquivo de script:

 #!/bin/bash /usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done; unset f;  

A funcionalidade acima também pode ser alcançada desta maneira (recomendada):

 #!/bin/bash while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done < <(/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0); unset f;  

Breve / Breve DESCRIÇÃO:

"./": este é o diretório atual. especifique um caminho de diretório.
-not -type d: aqui o -not o está configurando para ignorar o próximo tipo mencionado, & próximo mencionado -type é d = diretórios, então ele irá pular diretórios. Use f em vez de d para pular arquivos. Use l em vez de d para pular os arquivos de link simbólico.
-maxdepth 1: está configurando-o para localizar arquivos no nível de diretório atual (também conhecido como: um) apenas. Para localizar o arquivo dentro de cada & primeiro nível de subdiretório, defina maxdepth como 2. Se -maxdepth não for usado, ele pesquisará recursivamente (dentro de subdiretórios), etc.
-iname "*.jpg": aqui o -iname o está configurando para localizar arquivos e ignorar (superior / inferior) -caso no nome do arquivo / extensão. O -name não ignora maiúsculas e minúsculas. O -lname encontra links simbólicos. etc.
-print0: imprime o nome do caminho do arquivo atual na saída padrão, seguido por um caractere ASCII NUL (código de caractere 0), que iremos detectar mais tarde usando read em while.
IFS=: aqui é usado no caso de um nome de arquivo terminar com um espaço. Estamos usando NUL / "" / \0 com IFS para detectar cada nome de arquivo encontrado. Como ” find ” está configurado para separá-los com \0 que é produzido por -print0.
read -r -d $"\0" fileName: o $"\0" é "" / NUL. O -r foi usado no caso de um nome de arquivo ter uma barra invertida.
ler [-ers] [-a aname] [-d delim] [-i texto] [-n nchars] [-N nchars] [-p prompt] [-t tempo limite] [-u fd] [nome. ..] […]
-r A barra invertida não atua como um caractere de escape. A barra invertida é considerada parte da linha. Em particular, um par barra invertida-nova linha não pode ser usado como uma continuação de linha.
done < <(...): Substituição de processo usada aqui para enviar / saída de tubo de ” find ” em ” ler ” de ” enquanto ” -loop. Mais informações: https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html, https://wiki.bash-hackers.org/syntax/expansion/proc_subst, https://tldp.org/LDP/abs/html/abs-guide.html#PROCESS-SUB, https://tldp.org/LDP/abs/html/abs-guide.html#PSUBP


Em outra resposta de @ John1024, ele mostrou uma ótima solução baseada em bash, que não usa o ” find “, um utilitário externo.
” find ” é muito eficaz & rápido, eu prefiro quando há muitos arquivos para lidar.

na solução de @ John1024, ele imprimirá o linha de regra de correspondência quando não há nenhum arquivo no diretório, portanto, abaixo da linha [ ! -e "${f}" ]... é usada para pular isso,
aqui está uma solução de linha única para usar diretamente no Terminal:
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;

Aqui está um script:

 #!/bin/bash DIR="/home/john/my directory/"; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; # your codes/commands, etc }; done; unset DIR;  

Nota: se o diretório em DIR tiver "/" barra (indicador de diretório) no final, então em regra de correspondência , novamente usando o "/" não é necessário,
Ou, faça o oposto: no DIR, não use o "/" no final e então use-o na regra de correspondência "$DIR"/*.txt


Essa verificação extra usando o [ ! -e "${f}" ]... o código pode ser evitado, se abaixo de shell-option (também conhecido como: ” shopt “) é usado ou ativado:
shopt -s nullglob

Se o estado de um shopt foi alterado por um script, em outro programa de script baseado em bash ele cria problemas inesperados / imprevistos.

Para ter um comportamento consistente em todos os scripts que usa bash, o estado da opção do bash-shell deve ser registrado / salvo dentro do seu script, e assim que terminar de usar suas funções primárias no seu script, a opção do shell deve ser redefinida para as configurações anteriores.

Usamos backtick (também conhecido como: sotaque grave, também conhecido como: backquote) `...` substituição de comando (para códigos internos de comando bash, etc), para não gerar um novo sub-shell, retém o significado literal da barra invertida, para um suporte mais amplo (também conhecido como portabilidade), etc, Como comandos bash internos baseados em crase, etc, podem ser freqüentemente executados no mesmo shell que o script, etc, e por isso é um pouco mais rápido & melhor e também melhor para o propósito que estamos tratando aqui. Se você preferir $(...) substituição de comando então use-a, qualquer pessoa tem liberdade & direito de escolher o que preferir, evitar, etc. Mais informações : aqui .

O script acima é mostrado novamente, & desta vez com as configurações anteriores de um shopt restaurado, antes de terminar o script:

 #!/bin/bash DIR="/home/john/my directory/"; ub="/usr/bin"; # shopt = shell-option(s). # Setting-up "previous-nullglob" state to "enabled"/"on"/"1": p_nullglob=1; # The "shopt -s" command output shows list of enabled shopt list, so if # nullglob is NOT set to ON/enabled, then setting "previous_nullglob" as 0 [ "`shopt -s | ${ub}/grep nullglob`" == "" ] && p_nullglob=0; # Enabling shell-options "nullglob": shopt -s nullglob; # previous code, but without the extra checking [ ! -e "${f}" ]... line: for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; # As we have utilized enabled nullglob shopt, its now in enabled state, # so if previously it was disabled only-then we will disable it: [ "$p_nullglob" -eq "0" ] && shopt -u nullglob; unset DIR; unset p_nullglob ub;  

Resultado de shopt -p shoptName (por exemplo: shopt -p dotglob) pode ser
também, este: shopt -u shoptName (o u é unset / disabled / off / 0)
ou, isto: shopt -s shoptName ( o s é set / enabled / on / 1)
A posição da letra "s" ou "u" está sempre em 7 (porque, em bash, um a posição da letra da string “começa em 0, ou seja, a primeira letra de uma string está na posição 0)
Nós pode obter este "u" ou e armazená-lo em uma variável, para que possamos usá-lo para restaurar o estado anterior.
E se aplicarmos esta forma (mencionada acima) para salvar / restaurar o estado do shopt, então podemos evite usar a ferramenta externa "grep".

Para visualizar "txt" o arquivo que começa com ".", isto é, para visualizar o arquivo "txt" oculto, precisamos habilitar "dotglob" shopt.

Portanto, desta vez abaixo, "dotglob" está incluído & habilitado para mostrar arquivos "txt" OCULTOS:

 #!/bin/bash DIR="/home/john/my directory/"; p_nullglob="u"; pSS="`shopt -p nullglob`"; [ "${pSS:7:1}" == "s" ] && p_nullglob="s"; p_dotglob="u"; pSS="`shopt -p dotglob`"; [ "${pSS:7:1}" == "s" ] && p_dotglob="s"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; [ "$p_nullglob" == "u" ] && shopt -u nullglob; [ "$p_dotglob" == "u" ] && shopt -u dotglob; unset DIR; unset p_nullglob p_dotglob pSS;  

Há uma maneira mais simples de salvar / restaurar a loja opção / valor.
Isaac postou aqui , como salvar + restaurar Env / Shopt variable / option state / value.

Salvando o estado shopt de ” nullglob “:

... # your primary-function codes/commands, etc lines
Restaurando o estado shopt anterior de ” nullglob “, antes de sair do script:
eval "$p_nullglob" ;

Vários estados de shopt podem ser salvos desta forma:
p_multipleShopt="`shopt -p nullglob dotglob`";
e o processo de restauração é o mesmo de antes:
eval "$p_multipleShopt" ;

Salve TODOS os estados da loja desta maneira:
p_allShopt="`shopt -p`";
e o processo de restauração é o mesmo de antes:
eval "$p_allShopt" ;

Então aqui está outra solução baseada em bash:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; eval "$p_allShopt" ; unset DIR p_allShopt;  

Usar eval é seguro acima, pois a variável "$p_allShopt" não contém dados fornecidos por um usuário ou dados que não higienizado, essa var está mantendo a saída do comando interno do bash shopt.
Se você ainda quiser evitar eval, use abaixo solução:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; while IFS= read -a oneLine ; do { ${oneLine} ; }; done < <(echo "$p_allShopt") ; unset DIR p_allShopt oneLine;  

Poucos (outros) notáveis & relacionados SHOPT que podem ser úteis, são:

  • nocaseglob: se definido, o Bash corresponde aos nomes de arquivos em um não diferencia maiúsculas de minúsculas ao realizar a expansão do nome de arquivo.
  • nocasematch: se definido, o Bash faz a correspondência dos padrões sem diferenciação de maiúsculas e minúsculas ao realizar a correspondência ao executar case ou [[ comandos condicionais, ao realizar expansões de palavras de substituição de padrão ou ao filtrar possíveis completações como parte da conclusão programável.
  • dotglob: Se definido, o Bash inclui nomes de arquivos começando com ‘.’ nos resultados da expansão do nome do arquivo. Os nomes de arquivo ‘.’ e ‘..’ devem sempre ser correspondidos explicitamente, mesmo se dotglob estiver definido.
  • nullglob: Se definido , Bash permite que os padrões de nome de arquivo que não correspondem a nenhum arquivo se expandam para uma string nula, em vez de se expandirem para uma string nula, em vez de eles próprios.
  • extglob: Se definido, os recursos de correspondência de padrão estendido descritos acima (consulte Correspondência de padrões ) estão ativados.
  • globstar: Se definido, o padrão ‘**’ usado em um contexto de expansão de nome de arquivo corresponderá a todos os arquivos e zero ou mais diretórios e subdiretórios. Se o padrão for seguido por um ‘/’, apenas os diretórios e subdiretórios corresponderão.

Resposta

Quando você deseja processar um conjunto de arquivos, considere que o nome deles pode ser um espaço ou outro código de fuga estará lá. Portanto, antes de iniciar seu processo, como for loop ou find command defina IFS bash env variable como:

IFS=$(echo -en "\n\b") 

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *