Vamos dividir isso em tarefas menores. Precisamos
- crie um diretório
hello-world
, se ele não existir - identifique de forma confiável os arquivos que contêm o texto
hello
- mova de forma confiável os arquivos para o novo diretório.
Fazer um diretório é feito com mkdir
. Ele sempre se recusará a criar um diretório que exista (recusando-se a sobrescrever tais diretórios), mas ele também possui um sinalizador -p
, que, como mencionado em man mkdir
-p, --parents
no error if existing, make parent directories as needed
não se preocupa em informar se o diretório que você está tentando criar já existe.
Assim, o primeiro comando do nosso script pode ser (verifique se você está no diretório correto primeiro).
mkdir -p hello-world
O segundo passo é identificar os arquivos que queremos. O comando mais óbvio para procurar texto nos arquivos é grep
. Se quiséssemos apenas os nomes dos arquivos que contêm hello
, podemos usar o -l
flag, que, de acordo com man grep
-l, --files-with-matches
Suppress normal output; instead print the name of each input file
from which output would normally have been printed. The scanning
will stop on the first match.
suprime a saída normal. Mas, embora possamos nos safar para o seu exemplo, nós realmente não queremos os nomes dos arquivos como saída, porque a saída de um comando é apenas texto, e nomes de arquivos podem conter todos os tipos de caracteres (do espaço humilde ao exótico). newline) que fará com que o shell veja algo diferente do nome real do arquivo para o qual você deseja fazer algo e se comporte de uma maneira que você não queria. Por exemplo, tendo configurado um diretório de teste para o seu script, se eu adicionar um arquivo que tenha um espaço em seu nome, veja o que acontece:
$ echo hello > with\ space
$ ls
a b c d e f with space
$ for i in $(grep -l hello *); do echo "$i"; done
a
c
f
with
space
O arquivo with space
é tratado como dois arquivos separados.
Para identificar arquivos para que o shell faça alguma coisa com eles, devemos evitar a análise de nomes de arquivos. Poderíamos testar cada arquivo, ver se ele contém o texto e, se houver, movê-lo. Para fazer o loop de arquivos, como mostrado acima, podemos usar for
, que tem uma sintaxe como esta:
for var in things; do stuff $var; done
Dentro de um loop for
(e em qualquer outro lugar também), podemos usar o comando test
, que às vezes se parece com [
e também é igual a [[
para fazer declarações condicionais, e se você quisesse verificar Se o arquivo estava vazio ou não, ou era de um determinado tipo, eu aconselho você a usar o comando test
.
Mas você quer encontrar algum texto em particular, então devemos chamar grep
, mas nós só queremos saber se procurando hello
no arquivo foi bem-sucedido, então sabemos que temos que fazer algo com o arquivo.
Para fazer uma declaração condicional com base no sucesso de um comando, podemos usar if
. if
é frequentemente usado com test
/ [
/ [[
, mas você não precisa disso, porque grep
tem outro sinalizador útil:
-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately
with zero status if any match is found, even if an error was detected.
Zero em Bash significa sucesso. Então, para fazer o que queremos, poderíamos escrever algo como
if grep -q hello file; then mv file hello-world; fi
(o fi
é parte da sintaxe do comando if
. Ele informa ao shell que você terminou seu if
)
Eu normalmente escrevo scripts de shell em um shell interativo e apenas os scriptizo em um arquivo mais tarde, se de alguma forma, porque eu sou preguiçoso e principalmente escrevo scripts muito triviais. De qualquer forma, você pode escrever um script curto como um comando de uma linha separando os comandos com ;
. Se algo der errado, pressione a tecla de seta para cima para editar o último comando ...
Não queremos executar esse comando uma vez para cada arquivo. Isso frustraria o objetivo de escrever um script para fazer o trabalho e nos poupar da digitação, então, para fazer um loop pelos arquivos, podemos usar for
. Para evitar um erro de grep
sobre o diretório hello-world
, podemos adicionar outro sinalizador para excluí-lo.
Aqui está o meu comando de teste para fazer este trabalho e a saída que recebo dele no meu ambiente de teste:
$ mkdir -p hello-world; for file in *; do if grep -q --exclude-dir=hello-world -- hello "$file"; then echo mv -v -- "$file" hello-world; fi; done
mv -v -- a hello-world
mv -v -- c hello-world
mv -v -- f hello-world
mv -v -- with space hello-world
Este script não move nenhum arquivo, por causa de echo
antes de mv
, que mostra qual comando será executado a cada iteração do loop. Remova echo
depois de testar se os comandos parecem corretos (note que nem sempre é possível usar echo
para testes e talvez seja necessário introduzir algumas citações temporárias para fazer isso, mas funciona bem aqui).
*
é todos os arquivos não ocultos no diretório atual. É mais seguro usar ./*
, o que significa a mesma coisa, mas assegura que todos os caminhos iniciem com ./
(que é o endereço do diretório de trabalho atual .
), impedindo que nomes de arquivos começando com -
sejam interpretados como opções. Nós lidamos com essa possibilidade adicionando --
a grep
e ao comando mv
para indicar o fim das opções. -v
é o sinalizador detalhado de mv
.
Devemos citar variáveis para suprimir expansões de shell. Se eu remover as aspas em torno de "$file"
, minha saída será
mv -v -- a hello-world
mv -v -- c hello-world
mv -v -- f hello-world
grep: with: No such file or directory
grep: space: No such file or directory
Aqui está o script como um script:
#!/bin/bash
mkdir -p hello-world
for file in *; do
if grep -q --exclude-dir=hello-world -- hello "$file"; then
echo mv -v -- "$file" hello-world
fi
done
Não se esqueça de remover echo
quando estiver pronto para mover os arquivos para valer.
PS, pode haver maneiras mais eficientes de fazer isso. Meu caminho é apenas um exemplo.