Eu tenho um script de shell bash que percorre todos os diretórios filho (mas não arquivos) de um determinado diretório. O problema é que alguns dos nomes de diretório contêm espaços.
Aqui está o conteúdo do meu diretório de teste:
$ls -F test
Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ cities.txt
E o código que percorre os diretórios:
for f in `find test/* -type d`; do
echo $f
done
Aqui está a saída:
teste / Baltimore teste / cereja Colina teste / Edison teste / novo Iorque Cidade test / Filadélfia
Cherry Hill e New York City são tratadas como 2 ou 3 entradas separadas.
Tentei citar os nomes de arquivos, assim:
for f in `find test/* -type d | sed -e 's/^/\"/' | sed -e 's/$/\"/'`; do
echo $f
done
mas sem sucesso.
Deve haver uma maneira simples de fazer isso.
As respostas abaixo são ótimas. Mas para tornar isso mais complicado - nem sempre quero usar os diretórios listados no meu diretório de teste. Às vezes, quero passar os nomes de diretório como parâmetros da linha de comando.
Peguei a sugestão de Charles de definir o IFS e criei o seguinte:
dirlist="${@}"
(
[[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'\n'
for d in $dirlist; do
echo $d
done
)
e isso funciona bem, a menos que haja espaços nos argumentos da linha de comando (mesmo que esses argumentos sejam citados). Por exemplo, chamar o script assim: test.sh "Cherry Hill" "New York City"
produz a seguinte saída:
cereja Colina Novo Iorque Cidade
fonte
list="$@"
descarta completamente a lista do valor original, recolhendo-o em uma string. Por favor, siga as práticas em minha resposta exatamente como fornecidas - tal tarefa não é incentivada em nenhum lugar; se você quiser passar uma lista de argumentos da linha de comando para um programa, colete-os em uma matriz e expanda-a diretamente.Respostas:
Primeiro, não faça dessa maneira. A melhor abordagem é usar
find -exec
corretamente:A outra abordagem segura é usar a lista terminada por NUL, embora isso exija que você encontre suporte
-print0
:Você também pode preencher uma matriz de find e passar essa matriz mais tarde:
Se a sua descoberta não suportar
-print0
, o resultado será inseguro - o abaixo não se comportará como desejado se existirem arquivos contendo novas linhas em seus nomes (o que, sim, é legal):Se alguém não usar um dos itens acima, uma terceira abordagem (menos eficiente em termos de tempo e uso de memória, pois lê toda a saída do subprocesso antes de dividir palavras) é usar uma
IFS
variável que não contém o caractere de espaço. Desative globbing (set -f
) para impedir que cadeias contendo caracteres globos, como[]
,*
ou?
sejam expandidas:Finalmente, para o caso de parâmetro da linha de comando, você deve usar matrizes se o seu shell suportar (por exemplo, é ksh, bash ou zsh):
manterá a separação. Observe que a citação (e o uso de
$@
e não$*
) é importante. As matrizes também podem ser preenchidas de outras maneiras, como expressões glob:fonte
No entanto, não funciona se o nome do arquivo contiver novas linhas. A descrição acima é a única solução que eu sei quando você realmente deseja ter o nome do diretório em uma variável. Se você quiser apenas executar algum comando, use xargs.
fonte
find . -type d | while read file; do ls "$file"; done
Aqui está uma solução simples que lida com guias e / ou espaços em branco no nome do arquivo. Se você precisar lidar com outros caracteres estranhos no nome do arquivo, como novas linhas, escolha outra resposta.
O diretório de teste
O código para entrar nos diretórios
O nome do arquivo deve ser citado (
"$f"
) se usado como argumento. Sem aspas, os espaços atuam como separador de argumentos e vários argumentos são dados ao comando invocado.E a saída:
fonte
alias duc='ls -d * | while read D; do du -sh "$D"; done;'
alias duc='du -sh *(/)'
-print0
eIFS='' read -r -d '' f
.Isso é extremamente complicado no Unix padrão, e a maioria das soluções falha em novas linhas ou em algum outro personagem. No entanto, se você estiver usando o conjunto de ferramentas GNU, poderá explorar a
find
opção-print0
e usarxargs
com a opção correspondente-0
(menos-zero). Existem dois caracteres que não podem aparecer em um nome de arquivo simples; essas são barras e NUL '\ 0'. Obviamente, a barra aparece nos nomes dos caminhos, portanto a solução GNU de usar um NUL '\ 0' para marcar o final do nome é engenhosa e à prova de idiotas.fonte
Por que não colocar
na frente do comando for? Isso altera o separador de campos de <Espaço> <Guia> <Nova Linha> para apenas <Nova Linha>
fonte
fonte
-d $'\0'
é precisamente o mesmo que-d ''
- porque o bash usa cadeias terminadas em NUL, o primeiro caractere de uma cadeia vazia é uma NUL e, pelo mesmo motivo, as NULs não podem ser representadas dentro das cadeias C.eu uso
Isso não seria suficiente?
Ideia retirada de http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html
fonte
$(echo ...)
), não manipula nomes de arquivos com expressões globais corretamente, não manipula nomes de arquivos que contêm$'\b'
ou $ '\ n' caracteres corretamente e, além disso, converte várias execuções de espaço em branco em caracteres de espaço único no saída devido a cotação incorreta.Não armazene listas como strings; armazene-os como matrizes para evitar toda essa confusão de delimitadores. Aqui está um exemplo de script que irá operar em todos os subdiretórios de teste ou na lista fornecida em sua linha de comando:
Agora vamos tentar isso em um diretório de teste com uma ou duas curvas inseridas:
fonte
"$@"
matriz anexando-aset -- "$@" "$f"
.Você pode usar o IFS (separador de campo interno) temporariamente usando:
fonte
ps se for apenas sobre o espaço na entrada, algumas aspas duplas funcionaram sem problemas para mim ...
fonte
Para adicionar ao que Jonathan disse: use a
-print0
opção parafind
em conjunto comxargs
o seguinte:Isso executará o comando
command
com os argumentos adequados; diretórios com espaços neles serão citados corretamente (ou seja, serão passados como um argumento).fonte
O código acima irá converter arquivos .mov para .avi. Os arquivos .mov estão em pastas diferentes e os nomes das pastas têm espaços em branco . Meu script acima irá converter os arquivos .mov para o arquivo .avi na mesma pasta. Não sei se isso ajuda as pessoas.
Caso:
Felicidades!
fonte
echo "$name" | ...
não funciona sename
está-n
, e como ele se comporta com nomes com seqüências de escape de barra invertida depende da sua implementação - o POSIX torna o comportamentoecho
nesse caso explicitamente indefinido (enquanto o POSIX estendido por XSI faz a expansão do comportamento definido por padrão de seqüências de escape de barra invertida) e sistemas GNU - incluindo Bash - semPOSIXLY_CORRECT=1
pausa o padrão POSIX através da implementação-e
(ao passo que a especificação exigeecho -e
para imprimir-e
. na saída)printf '%s\n' "$name" | ...
é mais seguro.Também tinha que lidar com espaços em branco em nomes de caminho. O que finalmente fiz foi usar uma recursão e
for item in /path/*
:fonte
function
palavra-chave - ela torna seu código incompatível com o POSIX sh, mas não tem outro objetivo útil. Você pode apenas definir uma função comrecursedir() {
, adicionando os dois parênteses e removendo a palavra-chave da função, e isso será compatível com todos os shells compatíveis com POSIX.Converta a lista de arquivos em uma matriz Bash. Isso usa a abordagem de Matt McClure para retornar uma matriz de uma função Bash: http://notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html O resultado é uma maneira para converter qualquer entrada com várias linhas em uma matriz Bash.
Essa abordagem parece funcionar mesmo quando caracteres ruins estão presentes e é uma maneira geral de converter qualquer entrada em uma matriz Bash. A desvantagem é que, se a entrada for longa, você poderá exceder os limites de tamanho da linha de comando do Bash ou consumir grandes quantidades de memória.
As abordagens nas quais o loop que está trabalhando na lista também têm a lista em questão têm a desvantagem de que ler stdin não é fácil (como pedir entrada ao usuário) e o loop é um novo processo, então você pode estar se perguntando por que variáveis você definir dentro do loop não estará disponível depois que o loop terminar.
Também não gosto de configurar o IFS, ele pode atrapalhar outro código.
fonte
IFS='' read
, na mesma linha, a configuração IFS estará presente apenas para o comando de leitura e não escapará. Não há motivo para não gostar de configurar o IFS dessa maneira.Bem, eu vejo muitas respostas complicadas. Eu não quero passar a saída do utilitário find ou escrever um loop, porque o find tem a opção "exec" para isso.
Meu problema era que eu queria mover todos os arquivos com extensão dbf para a pasta atual e alguns deles continham espaço em branco.
Eu lidei com isso assim:
Parece muito simples para mim
fonte
acabei de descobrir que existem algumas semelhanças entre a minha pergunta e a sua. Aparentemente, se você deseja passar argumentos para comandos
para imprimi-los em ordem
observe que o $ @ está entre aspas duplas, algumas notas aqui
fonte
Eu precisava do mesmo conceito para compactar sequencialmente vários diretórios ou arquivos de uma determinada pasta. Eu resolvi usar o awk para analisar a lista de ls e evitar o problema de espaço em branco no nome.
O que você acha?
fonte
fonte
Para mim, isso funciona, e é praticamente "limpo":
fonte
Só tinha um problema de variante simples ... Converta arquivos de .flv digitados em .mp3 (bocejo).
encontre recursivamente todos os arquivos flash do usuário do Macintosh e transforme-os em áudio (cópia, sem transcodificação) ... é como o descrito acima, observando que a leitura em vez de apenas 'para o arquivo
' escapará.
fonte
read
depoisin
é mais uma palavra na lista que você está repetindo. O que você postou é uma versão ligeiramente quebrada do que o solicitante tinha, o que não funciona. Você pode ter a intenção de postar algo diferente, mas provavelmente está coberto por outras respostas aqui de qualquer maneira.