expansão do nome bashn / shell para mkdir, touch etc?

2

Portanto, fazer algo assim em bash e na maioria das outras shells não funcionará para criar vários subdiretórios ou arquivos dentro de subdiretórios ...

mkdir */test
touch */hello.txt

Existem, é claro, muitas maneiras de realmente fazer isso, meu preferido é usar find quando possível, em vez de usar um for loop, principalmente para legibilidade.

Mas a minha pergunta é: por que os itens acima não funcionam?
Pelo que entendi, é porque o arquivo / caminho de destino completo não existe, mas certamente isso é bom se eu estou tentando mkdir ou touch . Eu sempre mudei de lugar e nunca questionei isso.
Mas alguém tem uma explicação decente para isso que me ajudará a entender de uma vez por todas?

    
por batfastad 19.08.2014 / 12:26

3 respostas

7

O ponto que você pode estar perdendo - com o qual muitas pessoas têm problemas, especialmente se tiverem experiência com outros sistemas operacionais antes de chegarem ao * nix - é que, em muitos outros sistemas operacionais, os curingas na linha de comando são normalmente passados ao comando para processar como achar melhor. Por exemplo, no Prompt de Comando do Windows,

rename *.jpeg *.jpg

Considerando que, em * nix, a fim de simplificar o trabalho do (s) programador (es) dos comandos individuais (por exemplo, mv ), curingas (quando não citado ou escapado) são manipulados pelo shell, de uma maneira independente da interface e da funcionalidade do comando para o qual os caracteres curinga são argumentos. A maioria dos programas que usam nomes de arquivos como argumentos de linha de comando espera que esses arquivos existam (sim, mkdir e touch são exceções a essa regra, como são mkfifo , mknod e, em parte, cp , ln , mv e rename ; e provavelmente há outros) por isso não faz sentido para o shell expandir curingas para nomes de arquivos que não existem. E para o shell (e, por isso, eu quero dizer todo shell - Bourne, bash, csh, fish, ksh, zsh, etc ...) para lidar com as exceções de forma diferente provavelmente seria muito trabalhoso.

Dito isso, há algumas maneiras de obter um resultado como o que você deseja. Se você sabe para o que o curinga vai se expandir, e não é muito tempo, você pode gerá-lo com a expansão de chaves:

touch {red,orange,yellow,green,blue,indigo,violet}/rgb.txt

Uma solução mais geral:

sh -c 'for arg do mkdir -- "$arg"/test; done' -- *

Gilles ajudou-me a encontrar outro caminho para fazer isso no bash.

benbradley=(*)
mkdir "${benbradley[@]/%//test}"

Obviamente, benbradley é apenas um identificador aqui; você pode usar qualquer nome (por exemplo, qualquer letra única). Levei algumas tentativas para acertar, então deixe-me explicar:

  • %código% cria uma variável (escalar) chamada identifier=value (se ainda não existir) e atribui o valor identifier a ele. Por exemplo, value . Você pode referenciar uma variável escalar com G=Man ; por exemplo, $identifier ou, com mais segurança, como $G . Por exemplo, ${identifier} e $Gage podem ser indefinidos, mas $Gilla é ${G}age e Manage é ${G}illa .
  • Manilla cria uma variável de matriz chamada identifier=(value1 value2) (se ainda não existir) e atribui os valores listados a ele. Por exemplo,
spectrum=(red orange yellow green blue indigo violet)
ou
all_text_files=(*.txt)
identifier fará referência ao primeiro elemento (e, portanto, é bastante inútil). Você pode referenciar um elemento arbitrário como $name ; por exemplo, ${name[subscript]} é ${spectrum[0]} e red é ${spectrum[4]} . E você pode usar blue e ${name[@]} para referenciar toda a matriz.

Então, aqui está em pedaços:

"   ${   benbradley[@]   /   %   /   /test   }   "
  • %código% expande ${name[*]} e substitui a correspondência mais longa de ${parameter/pattern/string} com ${parameter} .
  • Se pattern começar com string , ele deve corresponder no início do valor expandido de pattern . Se # começar com parameter , ele deve corresponder ao final do valor expandido de pattern . Em outras palavras,
%(the-rest_of_the_pattern)
age como
(the-rest_of_the_regex)$
(sim, isso parece um pouco para trás). Então, um % que é apenas um parameter é como uma expressão regular que é apenas um pattern - ele corresponde ao final da string de entrada (espaço de pesquisa).
  • E eu estou usando um % de $ . Então, isso substitui o final do parâmetro por string (isto é, anexa /test ao parâmetro).
  • Outra coisa sobre %código% Isso não é intuitivo, pois sempre termina com /test . Não é necessário, e não pode terminar com um terceiro /test . Portanto, um ${parameter/pattern/string} em } não pode ser interpretado como um delimitador, e, portanto, podemos ter um / de / sem precisar escapar do string .
  • Se string for uma variável de matriz subscrito com /test ou / , a operação de substituição é aplicada a cada membro da matriz, por sua vez.
  • Quando você faz referência a uma matriz como parameter (em vez de @ ) e colocar os resultados em aspas duplas, você preserva a integridade dos elementos da matriz (isto é, você preserva espaços e outros caracteres especiais neles) sem combinar os elementos separados em uma palavra longa.
por 19.08.2014 / 21:00
2

O padrão de curinga é expandido pelo shell antes do comando ser chamado. Veja a resposta do G-Man para uma explicação completa.

A maioria dos shells requer alguma forma de comando intermediário para aplicar uma transformação de texto às correspondências. O Zsh oferece uma maneira de transformar as combinações rapidamente com seus qualificadores :

  • e ou + para executar código para cada correspondência, que pode alterar a correspondência modificando a sequência REPLY (ou até mesmo alterar o número de correspondências modificando o reply arrray)
  • : seguido por um modificador de expansão do histórico para aplicar algumas transformações incorporadas

Exemplos:

mkdir */(:s:/:/test:)     # takes advantage of the fact that each match ends with /, which it replaces by /test
mkdir *(/e:REPLY+=/test:)
    
por 20.08.2014 / 02:48
0

Isso não funciona porque o shell tenta expandir a string inteira */test que ainda não existe. Por padrão, a string é expandida para si mesma. Se você está usando Bash, você pode querer olhar para as várias opções de globbing para lidar com isso do jeito que você quiser.

    
por 19.08.2014 / 12:38