A citação de nomes de arquivos é suficiente para executar o `xargs sudo rm -rf`?

10

Eu escrevi um script que exclui todos, exceto os dois últimos arquivos em uma pasta:

#!/bin/bash
ls -1 --quoting-style=shell-always /path/to/some/folder \
    | head -n -2 \
    | xargs printf -- "'/path/to/some/folder/%s'\n" \
    | xargs sudo rm -rf

Este script será executado como um trabalho cron todos os dias.

O raciocínio é o seguinte:

  1. Obtenha uma lista de todos os arquivos usando ls -1(para que eu obtenha um arquivo por linha);

  2. Remova os dois últimos da lista usando head -n -2;

  3. Como lsimprime caminhos relativos, use o xargs printfobjeto para preceder o caminho da pasta e torná-lo um caminho absoluto;

  4. Envie-os para sudo rm -rfusar xargs.

Todos têm acesso a esta pasta, para que qualquer pessoa possa criar e excluir qualquer arquivo nessa pasta.

O problema é: sudo rm -rf é assustador. xargs sudo rm -rfé incrivelmente assustador.

Quero ter certeza de que ninguém pode danificar outras pastas / sistemas criando arquivos inteligentes a serem excluídos (acidentalmente ou de propósito). Não sei, algo inteligente como:

file with / spaces.txt

o que poderia resultar em um super assustador sudo rm -rf /.

EDIT: Meu erro, os nomes dos arquivos não podem conter /, portanto esse problema específico não aconteceria, mas a questão sobre se há ou não outros riscos ainda permanece.

É por isso que estou usando --quoting-style=shell-always, isso deve evitar truques com arquivos com espaços. Mas agora estou me perguntando se alguém poderia ser mais inteligente com espaços e aspas no nome do arquivo, talvez.

Meu script é seguro?


Nota: preciso sudoporque estou acessando a pasta remotamente (de uma unidade de rede mapeada usando mount) e não consegui fazê-la funcionar sem o sudo.

Pedro A
fonte
3
Você já pensou em fazer algo assim printf -- '%s\0' /path/to/some/folder/* | head -zn -2 | xargs -0 rm?
steeldriver
Pode um arquivo com o personagem /em nome ser criado Estou tentando conseguir isso de volta aqui
George Udosen
3
@ George Não, um nome de arquivo não pode conter uma barra.
Wjandrea #
Então, quando OP diz pessoa inteligente eu queria saber ...
George Udosen
6
Simplesmente porque você está analisando a lssaída, este já é um comando mal escrito, mesmo com a citação. lstambém usa locale para ordem de classificação, eu acho, então não vejo qual é o objetivo de headremover os últimos 2 (a menos que você esteja tentando se livrar .e do ..qual o iirc não seja permitido como argumento de rmqualquer maneira. Basta usar find /path/to/folder -type f delete. não sudose você executar a partir do cron - cron já está no nível raiz
Sergiy Kolodyazhnyy

Respostas:

10

No Linux, qualquer caractere é um nome de arquivo válido que constitui um caractere, exceto:

  • \0 (ASCII NUL): conforme usado para terminação de string em C
  • / (barra): como usado para separação de caminho

Portanto, sua abordagem definitivamente não funcionará em muitos casos, como você pode imaginar, por exemplo, ela lida com uma nova linha ( \n) no nome do arquivo? ( Dica: não ).

Algumas notas:

  • Não analise ls; use ferramentas dedicadas (existe pelo menos uma para a maioria dos casos de uso)
  • Ao lidar com nomes de arquivos, tente alavancar a saída separada por NUL fornecida por quase todas as ferramentas GNU que trabalham com esses dados
  • Tome cuidado ao canalizar, verifique se os dois programas podem entender as separações NUL
  • Sempre que você estiver invocando xargs, veja se consegue se safar find ... -exec; na maioria dos casos, você vai ficar bem com apenas findsozinho

Eu acho que isso vai fazer você ir por enquanto. A steeldriver já forneceu a ideia separada de NUL no comentário ( printf -- '%s\0' /path/to/some/folder/* | head -zn -2 | xargs -0 rm), use isso como ponto de partida.

heemail
fonte
Obrigado pela sua resposta :) Acho que você deve citar o comentário do steeldriver em vez de apenas mencioná-lo (já que os comentários não são permanentes). Vou dar uma olhada findtambém, obrigado pela sugestão.
Pedro #
Mas tenho uma pergunta: não entendo o que você quer dizer com "sua abordagem não funcionará definitivamente em muitos casos, como você pode imaginar" - "não funciona" como em "não é seguro" ou "não é seguro"? Como "sua abordagem" se refere a mim e não ao usuário mal-intencionado, e suas declarações anteriores estão a meu favor, estou confuso.
Pedro A
@PedroA Você é você :) Como eu disse, como todos os caracteres são válidos, exceto os dois mencionados, você deve ser capaz de imaginar os numerosos casos em que sua lsabordagem de análise falharia, por exemplo, você considerou uma nova linha no nome do arquivo?
heemayl
Ah, uma nova linha no nome do arquivo ... eu não tinha pensado nisso. Se você não se importa em adicionar isso à sua resposta também :) Além disso, desculpe-se por perguntar, mas o que head -zfaz? Parece ridículo, mas eu não tenho mannem infono linux do meu contêiner CoreOS ... Não foi possível encontrar na internet também. Eu recebohead: invalid option 'z'
Pedro A
O @PedroA Newline é apenas um caso, existem muitos como você deve ter adivinhado . Você precisa do GNU head(vem com o GNU coreutils). Aqui está a versão on-line: manpages.ubuntu.com/manpages/xenial/man1/head.1.html
heemayl
3

xargs suporta algumas citações: com aspas simples, aspas duplas ou barra invertida, o que permite aceitar argumentos arbitrários¹, mas com uma sintaxe diferente da sintaxe de aspas dos shell do tipo Bourne.

A implementação do GNU lsencontrada no Ubuntu não possui nenhum modo de citação compatível com o xargsformato de entrada.

É ls --quoting-style=shell-alwayscompatível com os shells ksh93, bash e zsh, citando a sintaxe, mas somente quando a saída de lsé interpretada pelo shell no mesmo código do idioma que lsera quando a produziu. Além disso, alguns códigos de idioma, como aqueles que usam BIG5, BIG5-HKSCS, GBK ou GB18030, devem ser evitados.

Então, com essas conchas, você pode realmente fazer:

typeset -a files
eval "files=($(ls --quoting-style=shell-always))"
xargs -r0a <(printf '%s\0' "${files[@]:0:3}") ...

Mas isso tem pouca vantagem sobre:

files=(*(N))                 # zsh
files=(~(N)*)                # ksh93
shopt -s nullglob; files=(*) # bash

O único caso em que isso se torna útil é quando você deseja usar a -topção de lsclassificar os arquivos por mtime / atime / ctime ou -S/ -V. Mas, mesmo assim, você também pode usar zsh's:

files=(*(Nom))

por exemplo, para classificar os arquivos por mtime (use oLpara -Se npara -V).

Para remover todos, exceto os dois arquivos regulares modificados mais recentemente:

rm -f -- *(D.om[3,-1])

Still ainda existem algumas limitações de comprimento (por execve()algumas xargsimplementações não-GNU e arbitrárias muito mais baixas), e algumas xargsimplementações não-GNU sufocam as entradas que contêm sequências de bytes que não formam caracteres válidos.

Stéphane Chazelas
fonte