
Quero excluir arquivos que possuem a extensão size = 0
. Então eu tentei:
find ./ -size 0 | xargs rm
Porém, há um problema com arquivos cujos nomes começam com espaço.
Pesquisando na internet encontrei isso:
find ./ -size 0 -exec rm -i {} \;
Funciona. Porém, acho que meu jeito de usar xargs
é sofisticado demais para isso.
O que {} \;
significa?
Alguém poderia me explicar?
Meu inglês não é muito bom, então use uma escrita simples.
Responder1
{}
não tem absolutamente nenhum significado para bash
, então é passado sem modificação como um argumento para o comando executado, aqui find
.
Por outro lado, ;
tem um significado específico para bash
. Normalmente é usado para separar comandos sequenciais quando eles estão na mesma linha de comando. Aqui, a barra invertida \;
é usada precisamente para evitar que o ponto-e-vírgula seja interpretado como um separador de comando bash
e, em seguida, permitir que ele seja passado como um parâmetro para o comando subjacente, find
. Citar o ponto e vírgula, ou seja, ";"
ou ';'
, poderia ter sido uma forma alternativa de mantê-lo sem processamento.
O comando:
find ./ -size 0 -exec rm -i {} \;
significa: encontre no diretório atual (observe que é /
inútil aqui, .
não pode ser nada além de um diretório) qualquer coisa que tenha tamanho 0 e para cada objeto encontrado, execute o comando rm -i name
, ou seja, solicite interativamente cada arquivo se você deseja remover isto. {}
é substituído por cada nome de arquivo encontrado no comando executado. Um recurso interessante é que esse nome de arquivo é estritamente um único argumento, qualquer que seja o nome do arquivo (mesmo contendo espaços incorporados, tabulações, feeds de linha e quaisquer caracteres). Este não seria o caso xargs
, a menos que hacks não portáteis sejam usados. A final ;
existe para encerrar a -exec
cláusula. A razão pela qual o seu fim precisa ser delimitado é que outras find
opções podem seguir-se a -exec
essa, embora isso raramente seja feito. por exemplo:
find . -name "*.js" -exec ls -l {} \; -name "special*" -exec wc -l {} \;
Um problema com este comando é que ele não ignora arquivos não simples, portanto, pode solicitar que o usuário exclua soquetes, dispositivos de blocos e caracteres, canais e diretórios. Sempre falhará com o último, mesmo se você responder sim.
Outra questão, embora não seja realmente crítica aqui, é que rm
será chamado para cada arquivo que tenha tamanho zero. Se você substituir a -exec
terminação de /;
por +
, find
otimizará a criação do subprocesso chamando apenas rm
o número mínimo possível de vezes, geralmente apenas uma vez.
Aqui está como eu modificaria este comando:
find . -type f -size 0 -exec rm -i {} +
Responder2
Ao usar find -exec
, {}
é expandido para cada resultado encontrado.
Por exemplo, se você tiver um diretório example
contendo 3 arquivos a.txt
, b.txt
e c.txt
, find example/ -exec rm -i {} \;
será expandido para:
find example/ -exec rm -i example/a.txt \;
find example/ -exec rm -i example/b.txt \;
find example/ -exec rm -i example/c.txt \;
O \;
final é simples e escapado ;
para indicar o final do padrão exec. Caso contrário, seria interpretado pelo próprio shell.
Responder3
Em conjunto com a opção find
do comando , a parte é substituída pelo nome dos arquivos encontrados quando o comando é executado. O também é importante, pois é isso que define o final do comando que está sendo executadoexec
{}
\;
Por exemplo
find ~ -name \*.bak -exec -rm -f {} \;
excluiria todos os arquivos que terminassem em .bak
qualquer lugar do diretório inicial do usuário ou nas pastas contidas nele. Executando rm -f
em cada arquivo encontrado.
xargs
pega linhas de entrada padrão, geralmente de um pipe, e forma a parte final dos argumentos dele quando executa o comando que você fornece
Responder4
Esta é uma pergunta antiga, mas quero adicionar mais algumas informações:
find ./ -size 0 -exec rm -i {} \;
No comando anterior, \;
é um ponto e vírgula com escape. Isso evita que o comando seja manipulado pelo shell (ou seja, normalmente ;
separaria comandos).
O -exec
argumento interpreta tudo como um comando até aquele ponto e vírgula escapado \;
(ou seja, rm -i {}
será um comando interno a ser executado por find
). Dentro do comando interno {}
representa a expansão do parâmetro. Em inglês, significa “insira o nome do arquivo encontrado aqui”.
Portanto, se os arquivos encontrados fossem "arquivo-a.txt" e "arquivo-b.txt", find
seria executado rm -i file-a.txt
então rm -i file-b.txt
.
Um problema com este comando é que ele não ignora arquivos não simples, portanto, pode solicitar que o usuário exclua soquetes, dispositivos de blocos e caracteres, canais e diretórios. Sempre falhará com o último, mesmo se você responder sim (ou seja, os diretórios precisam ser excluídos recursivamente)
Outra questão, embora não seja realmente crítica aqui, é que rm
será chamado para cada arquivo que tenha tamanho zero. Se você substituir a -exec
terminação de /;
por +
, find otimizará a criação do subprocesso chamando apenas rm
o número mínimo possível de vezes, geralmente apenas uma vez.
Aqui está como eu modificaria este comando:
find ./ -type f -size 0 -exec rm -i {} +
curly brackets
ou braces
: {}
pode ser usado de maneiras diferentes
- Expansão de cinta.
- Comandos de agrupamento
- Expansão de parâmetros
- Espaço reservado para texto
- Nome do caminho
Expansão de cinta
Chaves podem ser usadas para construir sequências:
### prints out the numbers from 0 to 10
echo {0..10}
## prints out the same numbers, but in reverse order
echo {10..0}
## prints every second number, from 10 to 0
echo {10..0..2}
## prints every second letter, from z and working its way backwards to a.
echo {z..a..2}
Também podemos combinar duas ou mais sequências:
## prints out a pair of letters, from aa to zz.
echo {a..z}{a..z}
Adicionando prefixos e sufixos:
### adds '"' as prefix and suffix
echo \"{These,words,are,quoted}\"
# output: "These" "words" "are" "quoted"
# concatenates the files file1, file2, and file3 into combined_file.
cat {file1,file2,file3} > combined_file
# copies "file22.txt" to "file22.backup"
cp file22.{txt,backup}
Observação:
Nenhum espaço é permitido entre chaves, {...}
a menos que os espaços sejamcitadoouescapou.
echo {file1,file2}\ :{\ A," B",' C'}
# output: file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C
Expansão estendida do Brace.
Chaves podem ser usadas para construir matrizes. Arrays no Bash são definidos colocando os elementos entre parênteses ()
e separando cada elemento usando um espaço, assim:
month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
Para acessar um elemento dentro do array, você usa seu índice entre colchetes []
:
# Array indexes start at [0], so [3] points to the fourth item
$ echo ${month[3]}
## output: Apr
Portanto, podemos criar um array com algo assim:
## builds an array that contains all the 2-letter combinations of the entire alphabet.
letter_combos=({a..z}{a..z})
## contains all the binary numbers for an 8-bit register, in ascending order,
## from 00000000, 00000001, 00000010, etc., to 11111111.
dec2bin=({0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1})
Este último é particularmente interessante porque agora podemos usar dec2bin para construir um conversor decimal para binário de 8 bits. Digamos que você queira saber quanto é 25 em binário. Você consegue fazer isso:
$ echo ${dec2bin[25]}
## output: 00011001
Mas Teo, não existem maneiras melhores de converter decimal em binário?
- Sim, existem, mas ainda é interessante, certo?
Comandos de agrupamento
{ ... }
pode ser usado para colocar uma lista de comandos a serem executados no contexto atual do shell. Nãosub-shellé criado. O ponto e vírgula ;
(ou nova linha) da lista a seguir éobrigatório.
Parênteses ()
são usados para executar comandos em umsub-shell:
menu_type=bar
echo $menu_type
## output: bar
## new lets called in a sub-shell
(menu_type=superbar; echo $menu_type)
## output: superbar
## back to the context
echo $menu_type
## output: bar
Não podemos obter acesso ao novo valor superbar
de menu_type
.
No entanto, se executarmos algo assim:
{ menu_type=superbar; echo $menu_type; }
## output: superbar
echo $menu_type
## output: superbar
{ ... }
não cria um sub-shell, por isso podemos ter acesso ao menu_type
valor.
{ ... }
também são chamados de inline group
, na verdade, ele cria uma função anônima (ou seja, uma função sem nome). Em inglês simples, ao contrário de uma função "padrão", as variáveis dentro de a { ... }
permanecem visíveis para o restante do script.
Além disso, { ... }
pode ser usado para agrupar a saída de vários comandos stdout
ou para receber um redirecionamento em seu arquivo stdin
. Vejamos um exemplo:
#!/bin/bash
# rpm-check.sh
# Queries an rpm file for description, listing, and whether it can be installed.
# Saves output to a file.
SUCCESS=0
E_NOARGS=65
if [ -z "$1" ]; then
echo "Usage: `basename $0` rpm-file"
exit $E_NOARGS
fi
{ # Begin command group.
echo
echo "Archive Description:"
rpm -qpi $1 # Query description.
echo
echo "Archive Listing:"
rpm -qpl $1 # Query listing.
echo
rpm -i --test $1 # Query whether rpm file can be installed.
if [ "$?" -eq $SUCCESS ]
then
echo "$1 can be installed."
else
echo "$1 cannot be installed."
fi
echo # End command group.
} > "$1.test" # Redirects output of everything in block to file.
echo "Results of rpm test in file $1.test"
exit 0
Agora, vamos ver como proceder com um redirecionamento de I/O no grupo stdin
:
#!/bin/bash
File=/etc/fstab
## reads the first two lines of the file
{
read line1
read line2
} < $File
echo "First line in $File is:"
echo "$line1"
echo
echo "Second line in $File is:"
echo "$line2"
exit 0
Outro exemplo de como salvar a saída de um grupo de comandos em um arquivo
## exec commands sequentially and redirects the output of the ls command into the png-list.txt file
echo "I found all these png files:"; find . -iname "*.png"; echo "Within this bunch of files:"; ls > png-list.txt
## exec commands sequentially and redirects the output of the group into the png-list.txt file
{ echo "I found all these png files:"; find . -iname "*.png"; echo "Within this bunch of files:"; ls; } > png-list.txt
Qual é a diferença, Téo?
Bem, jovem padawan. O segundo cria o arquivo png-list.txt
com todas as saídas, começando com a linha “I found all these png files:“
e terminando com a ls
saída do comando.
Hack de sub-shell
Bash cria um sub-shell para um { ... }
comando de grupo de chavesse e apenas sefaz parte do pipeline, por exemplo:
$ { A=1; { A=2; sleep 2; } ; echo $A; }
## output: 2
$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
## output: 1
Observação:
Há espaço entre as chaves e a lista de comandos entre elas. Isso porque {
e }
são palavras reservadas (ou seja, comandos embutidos no shell). Além disso, a lista de comandos deve terminar com ponto e vírgula ;
ou usar novas linhas para separar comandos.
Expansão de parâmetros
Ok, voltando ao
month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
echo ${month[3]}
## output: Apr
Aqui, os colchetes {}
não estão sendo usados como parte de um construtor de sequência, mas como uma forma de gerar expansão de parâmetros. A expansão dos parâmetros envolve o que diz na caixa:
ele pega a variável ou expressão entre colchetes e a expande para tudo o que ela representa.
O que isso significa, Teo?
Bem, isso significa ${...}
que o shell expande tudo o que está dentro dele. Neste caso, month
é o array que definimos anteriormente, ou seja:
month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
E o item 3
dentro da matriz aponta para "Apr"
(ou seja, o primeiro índice em uma matriz no Bash é [0]
). Isso significa que echo ${month[3]}
, após a expansão, se traduz em echo "Apr"
.
Interpretar uma variável como seu valor é uma forma de expandi-la, mas existem mais algumas que podemos aproveitar. Podemos usar a expansão de parâmetros para manipular o que você lê da variável (ou seja, cortando um pedaço do final).
Suponha que você tenha uma variável como:
a="This sentence is too longgg"
## chops off the last two gs
echo ${a%gg}
## output: This sentence is too long
Isto pode ser útil para converter arquivos de um formato para outro. Por exemplo, se tivermos um comando que pega uma imagem JPEG chamada image.jpg
e a converte em uma imagem PNG chamada image.png
:
convert image.jpg image.png
Podemos reescrever assim:
i='image.jpg'
## chops off the extension 'jpg' and adds 'png'
convert $i "${i%jpg}png"
## output: convert image.jpg image.png
Mas como isso pode ser mais útil do que apenas escrever o nome do arquivo?
Bom, quando temos um diretório contendo centenas de imagens JPEG, você precisa converter para PNG, basta executar o seguinte nele:
for i in *.jpg; do convert $i ${i%jpg}png; done
… e todas as imagens são convertidas automaticamente. Seja bem-vindo jovem padawan.
Se você precisar cortar um pedaço do início de uma variável, em vez de %
, use #
:
$ a="Hello World!"
## chops off the word 'Hello' and adds 'Goodbye'
$ echo Goodbye${a#Hello}
## output: Goodbye World!
Espaço reservado para texto
Usado depois xargs -i
(ou seja, opção de substituição de strings). As {}
chaves duplas são um espaço reservado para o texto de saída.
## Execute 'echo ./<file>' for each file in the directory
ls . | xargs -i -t echo ./{} $1
# ^^ ^^
Nome do caminho
Um nome de caminho é um nome de arquivo que inclui o caminho completo. Como um exemplo, /home/<user>/Notes/todo.txt
. Isso às vezes é chamado de caminho absoluto. Encontraremos {}
principalmente em find
construções que contêm -exec <command> \;
. No entanto, este não é um shell integrado. Se <command>
contém {}
, find substitui o nome do caminho completo do arquivo selecionado por "{}"
.
# Removes all core dump files from user's home directory.
find ~/ -name 'core*' -exec rm {} \;