Comando xargs: transformar listas em comandos no Linux

Imagem destacada do artigo sobre o comando xargs no Linux: terminal com o comando xargs -0 e a frase «da lista aos argumentos, em segurança»

Quase todos os pipelines acabam por esbarrar na mesma parede: o comando de que precisamos não lê da entrada padrão. O find encontrou os ficheiros, o grep filtrou a lista — e agora o rm, o cp ou o ssh só aceitam nomes escritos na linha de comando. É aqui que entra o xargs, a cola invisível que transforma uma lista em argumentos.

Neste artigo vemos as opções que contam no dia a dia — agrupar, posicionar com um marcador, separar com segurança, correr em paralelo e controlar o que corre — e, como sempre no blog HJFR, o ângulo de segurança: nomes de ficheiros maliciosos que transformam uma limpeza inocente num incidente.

Veja o guia em vídeo

Prefere ver antes de experimentar? No canal HJFR-IT no YouTube mostramos o xargs opção a opção, com o resultado de cada comando no ecrã — narrado em português e com legendas.

xargs — Guia Completo: construir linhas de comando a partir de input — canal HJFR-IT no YouTube. Se o áudio não arrancar em português, abra as definições do vídeo (ícone da roda dentada) → Faixa de áudio → Português.

O que faz o xargs

O xargs lê itens da entrada padrão (ou de um ficheiro), separa-os e constrói com eles linhas de comando, que executa — com o echo, se não indicarmos comando. Por omissão, separa por espaços e quebras de linha, trata aspas e barra invertida como especiais e junta tantos argumentos quantos couberem numa só invocação.

xargs [OPÇÕES] [COMANDO [ARGUMENTOS-INICIAIS]]

produtor | xargs comando        # a lista vem do stdin
xargs -a lista.txt comando      # a lista vem de um ficheiro

O problema que o xargs resolve

Muitos comandos — rm, cp, mkdir — não leem da entrada padrão: esperam os nomes como argumentos. Mandar-lhes uma lista por um cano não faz nada. O xargs põe-se no meio: recebe a lista pela entrada e escreve-a como argumentos do comando.

$ echo 'nota1.txt nota2.txt' | rm
rm: missing operand
Try 'rm --help' for more information.

$ echo 'nota1.txt nota2.txt' | xargs rm     # corre: rm nota1.txt nota2.txt

Agrupar argumentos: -n e -L

Sem opções, o xargs junta tudo numa só linha de comando. A opção -n controla quantos argumentos entram em cada invocação: com -n 2, o comando corre várias vezes, dois argumentos de cada vez — útil quando há limite de argumentos ou queremos processar aos pares.

$ seq 6 | xargs
1 2 3 4 5 6
$ seq 6 | xargs -n 2
1 2
3 4
5 6
$ printf '1 2\n3\n4 5 6\n' | xargs -L 1
1 2
3
4 5 6

Com -n 2 a saída ficou em três linhas: foram três execuções do echo. Já a -L conta linhas em vez de argumentos: cada linha da entrada dá uma invocação, com as palavras que tiver.

O marcador -I (e -a para ler de um ficheiro)

Por omissão, o xargs põe os argumentos no fim do comando, mas muitas vezes precisamos deles no meio. O -I define um marcador — tradicionalmente as chavetas — que representa cada item e pode aparecer onde e quantas vezes quisermos.

# o nome aparece na origem e no destino da cópia
ls *.pdf | xargs -I {} cp {} backup/{}

# qualquer texto serve de marcador; aqui, H de host
xargs -a servidores.txt -I H ssh H uptime
  • -I implica uma invocação por item, e cada linha é um item: um nome como plano 2027.pdf chega inteiro (uma quebra de linha no nome continua a parti-lo).
  • -a FICHEIRO lê os itens de um ficheiro em vez do stdin, dispensa o cat e deixa a entrada padrão livre para o comando.

Segurança com nulo: -0, find -print0 e -d

A armadilha dos espaços

Aqui está a armadilha mais importante do xargs. Ele separa os itens por espaços e quebras de linha — mas os nomes de ficheiros podem conter ambos. Um ficheiro chamado o meu ficheiro.tmp é lido como três nomes, e o comando falha ou, pior, apaga o que não devia.

$ find cache -name '*.tmp' | xargs rm
rm: cannot remove 'cache/o': No such file or directory
rm: cannot remove 'meu': No such file or directory
rm: cannot remove 'ficheiro.tmp': No such file or directory

A correção: -print0 e -0

A solução é o par mágico: o find com -print0 termina cada nome com o caráter nulo — invisível e impossível num nome de ficheiro — e o xargs com -0 lê da mesma forma (e deixa de tratar aspas como especiais). Use sempre este par ao combinar find e xargs.

find cache -name '*.tmp' -print0 | xargs -0 rm

Separador à medida: -d

Quando a lista vem de um ls ou de um ficheiro de texto, a opção -d define o separador. Com -d '\n', cada linha é um item, espaços incluídos, e um apóstrofo como em caixa d'água.jpg deixa de dar unmatched single quote. Contra quebras de linha no nome, porém, só o -0.

ls fotos/*.jpg | xargs -d '\n' du -h

Correr em paralelo: -P

Uma capacidade que poucos conhecem e que vale ouro: com -P 4, o xargs mantém quatro cópias do comando a correr ao mesmo tempo e lança a seguinte assim que uma termina. Numa lista de mil imagens ou de dezenas de servidores, isto transforma minutos em segundos, aproveitando todos os núcleos do processador.

# descarregar quatro endereços em simultâneo
cat urls.txt | xargs -P 4 -n 1 curl -sO

# consultar quatro servidores ao mesmo tempo
xargs -a servidores.txt -P 4 -I H ssh H uptime

Combine -P com -n ou -I (sem eles, tudo cabe numa só invocação) e conte com a saída pela ordem de conclusão, não pela ordem da lista.

Controlo e segurança: -t, -p e -r

  • -t (de trace) imprime cada comando no stderr antes de o executar — essencial antes de uma operação destrutiva. No GNU xargs atual, os argumentos com espaços aparecem entre aspas.
  • -p vai mais longe: mostra cada comando e só o executa se respondermos y.
  • -r (--no-run-if-empty) evita um problema clássico: sem ele, se a lista chegar vazia, o xargs do GNU corre o comando à mesma, sem argumentos. Com -r, uma lista vazia não faz nada.
$ ls *.bak | xargs -t rm
rm db.conf.bak site.conf.bak

$ (cd uploads && ls | xargs -p rm)
rm fatura.pdf logo.png -rf thumbs?...n

$ find cache -name '*.swp' -print0 | xargs -0 -r rm    # lista vazia: nada corre

Exemplo real: redimensionar imagens em paralelo

Juntemos tudo para reduzir a metade todas as imagens de uma árvore de pastas. O find emite os .jpg separados por nulo; o xargs lê-os em segurança com -0, corre quatro conversões em simultâneo com -P 4 e usa o marcador para pôr o nome na origem e no destino do convert, do ImageMagick.

find fotos -name '*.jpg' -print0 | xargs -0 -P 4 -I {} convert {} -resize 50% {}

Uma linha substitui um ciclo inteiro num script. Atenção: com a mesma origem e destino, o original é substituído — experimente primeiro numa cópia.

xargs em cibersegurança

Os nomes de ficheiros são dados não confiáveis. Num diretório onde outros escrevem — uploads de um site, /tmp, partilhas — quem escolhe os nomes pode ser um atacante. Um ficheiro chamado $'x\nrelatorio.pdf\nx.tmp' é apanhado por find uploads -name '*.tmp'; sem -print0, o xargs parte-o nas quebras de linha e entrega relatorio.pdf ao rm, que apaga um ficheiro alheio à limpeza. Num script a correr como root, o alvo pode ser bem pior. Com -print0 | xargs -0, o nome é um único argumento.

Nomes que se fazem passar por opções. Se a lista vier do ls, um ficheiro chamado -rf chega ao rm como opção: rm fatura.pdf logo.png -rf thumbs apaga thumbs/ inteiro, em silêncio. A defesa é dupla: -- antes dos nomes (xargs rm --) marca o fim das opções, e caminhos com prefixo, como os que o find produz, nunca começam por traço.

Resposta a incidentes em escala. Do lado de quem defende, o xargs acelera a triagem: calcular hashes de uma árvore inteira (find /var/www -type f -print0 | xargs -0 sha256sum > hashes.txt), recolher o mesmo indicador de dezenas de servidores com -P, ou isolar suspeitos com xargs -0 -I {} mv -- {} /root/quarentena/. O -t deixa rasto do que correu — também é evidência.

Experimente no simulador interativo

O simulador recria um diretório com armadilhas: um nome com espaços em cache/ e, em uploads/, os nomes maliciosos -rf e um com quebras de linha. À esquerda vê os itens que o xargs leu (os perigosos a laranja), as invocações que construiu — no -P, uma pista por via — e o efeito no diretório; à direita, o terminal. Use Demonstrar tudo para a sequência guiada, Anterior e Seguinte para navegar, ou Repor estado para recomeçar.

Erros comuns e boas práticas

  • Usar find | xargs sem -print0 e -0: funciona nos testes e falha no primeiro nome com espaços. Use o par — ou find -exec … {} +.
  • Passar listas do ls a comandos destrutivos: nomes sem caminho podem começar por -. Prefira o find e ponha -- antes dos nomes.
  • Esquecer o -r em scripts: com lista vazia, um xargs du -sh mede o diretório atual em vez de nada.
  • Confundir -n com -L: -n conta argumentos, -L conta linhas — e nenhum protege contra espaços.
  • Paralelizar sem pensar na saída: com -P, as linhas chegam pela ordem de conclusão; inclua o nome do item em cada linha.
  • Executar à primeira: antes de um rm em massa, corra a mesma linha com xargs -t echo rm ou com -p.

Conclusão

O xargs transforma listas em argumentos: -n e -L para agrupar, -I para o marcador, -a e -d para a origem e o separador, -0 para nomes difíceis, -P para o paralelismo e -t, -p e -r para o controlo. É a cola por trás de metade dos pipelines poderosos em Linux — e, mal usado, uma das formas mais rápidas de apagar o que não devia. Combine-o com o comando find, que produz as listas, e com os pipelines de texto com cut, sort, uniq e wc, que as filtram.

Workshop — Linha de Comandos Linux

Quer passar da leitura à prática, com exercícios reais e acompanhamento? No workshop Linha de Comandos Linux da HJFR trabalha estes e outros comandos da série — da administração do dia a dia à resposta a incidentes.

Este artigo faz parte da série «Comandos Linux, um a um» do blog HJFR — fique atento aos próximos comandos.