Ls sempre listará os arquivos que a rm removerá?

33

Algo que sinto que devo ter certeza: se remover ls <something>, rm <something>removerei exatamente os mesmos arquivos lsexibidos? Existe alguma circunstância em que você rmpossa remover arquivos que lsnão foram exibidos? (Isso é no bash 18.04)

Edit: obrigado a todos que responderam. Penso que a resposta completa é uma combinação de todas as respostas, pelo que aceitei a resposta mais votada como "a resposta".

Coisas inesperadas que aprendi ao longo do caminho:

  • ls não é tão simples quanto você imagina ao lidar com seus argumentos
  • Em uma instalação simples e sem complicações do Ubuntu, aliases .bashrc ls
  • Não nomeie seus arquivos começando com um hífen, pois podem parecer argumentos de comando, e nomear um -r é pedir!
B.Tanner
fonte
8
Estou um pouco surpreso que rmnão tenha uma --dry-runbandeira ...
fkraiem
20
@Rinzwind Por que find -deleteseria melhor do que rm? Você diz "É por isso" , mas não está totalmente claro para mim a que isso se refere. Observe também que sua findchamada excluirá todos os arquivos recursivamente no diretório atual, onde rmapenas excluirá os arquivos no diretório imediato. Também -name *é um no-op. Tudo em tudo, estou muito intrigado com o seu conselho ...
marcelm
3
@ marcelm Acho que o conselho para o uso findé que você pode executá-lo, ver todos os arquivos e, em seguida, executar o mesmo comando com -delete. Desde que você já viu os resultados find, não deve haver nenhuma ambigüidade ao que será removido (que eu realmente gostaria de ouvir mais detalhes sobre isso na forma de uma resposta)
Scribblemacher
5
@Scribblemacher "... você pode executá-lo, ver todos os arquivos e, em seguida, executar o mesmo comando com -delete" - Mas como isso é melhor do que executar ls <filespec>, seguido por rm <filespec>(que o OP já sabe como fazer)?
Marcelm # 5/18
12
@Rinzwind "Isso resolve a resposta do choroba por um instante em que um arquivo é criado após ls e antes de rm." - Não, não faz. Se você executar find ... -printprimeiro para confirmar quais arquivos serão excluídos e find ... -delete, em seguida , ainda excluirá os arquivos criados entre os dois comandos. Se você usa os dois -printe -deletenão obtém confirmação, apenas um relatório posterior do que foi excluído (e você também pode usar rm -v).
Marcelm

Respostas:

40

Bem, ambos lse rmoperam com os argumentos que lhes são passados.

Estes argumentos podem ser um arquivo simples, tão ls file.exte rm file.extoperar o mesmo arquivo e o resultado é claro (lista o arquivo / excluir o arquivo).

Se, ao invés argumento é um diretório, ls directorylista o conteúdo do diretório enquanto rm directorynão funcionará como está (ou seja, rmsem bandeiras não pode remover diretórios, enquanto que se você fizer rm -r directory, ele recursivamente apaga todos os arquivos sob directory eo próprio diretório ).

Mas lembre-se de que os argumentos da linha de comando podem estar sujeitos à expansão do shell , portanto, nem sempre é garantido que os mesmos argumentos sejam transmitidos aos dois comandos se eles contiverem caracteres curinga, variáveis, saída de outros comandos etc.

Como exemplo extremo, pense ls $(rand).txte rm $(rand).txt, os argumentos são "os mesmos", mas os resultados são bem diferentes!

Mr Shunz
fonte
3
Considere também lsvs rm *onde há arquivos "ocultos" (ponto), embora mesmo isso não seja uma comparação justa, pois eu não escrevi ls *. Mas rmnão tem sentido por si só, então a coisa toda é realmente maçãs e laranjas. Se eu entendi corretamente, esse é o ponto crucial da sua resposta, tão bom trabalho :)
Leveza raças com Monica
5
Outro comentário sobre lsvs rm -r: o comando ls <directory>não vai mostrar arquivos escondidos dentro do diretório, mas rm -r <directory> vai eliminar até mesmo os arquivos ocultos.
Daniel Wagner
1
O @LightnessRacesinOrbit lsnão os listará (a menos que seja aliasado ls -a) e rm *não os excluirá (a menos que você tenha dotglobdefinido).
Pare de prejudicar Monica
20

Se você estiver pensando em algo como ls foo*.txtvs. rm foo*.txt, sim, eles mostrarão e removerão os mesmos arquivos. O shell expande a glob e a passa para o comando em questão, e os comandos funcionam nos arquivos listados. Um listando-os, um removendo-os.

A diferença óbvia é que, se algum desses arquivos fosse um diretório, lsele listaria seu conteúdo, mas rmfalharia em removê-lo. Isso geralmente não é um problema, pois rmremoveria menos do que o mostrado por ls.

O grande problema aqui vem da execução ls *ou rm *em um diretório que contém nomes de arquivos começando com um traço . Eles iriam se expandir para as linhas dos dois programas de comando como se você escreveu-los você mesmo, e lslevaria -rpara significar "ordem de classificação inversa", enquanto que rmlevaria -ra significar uma remoção recursiva. A diferença importa se você tiver subdiretórios com pelo menos dois níveis de profundidade. ( ls *mostrará o conteúdo dos diretórios de primeiro nível, mas também rm -r *passará tudo além do primeiro subnível.)

Para evitar isso, escreva globs permissivos com uma ./indicação para indicar o diretório atual e / ou coloque a --para sinalizar o final do processamento da opção antes da glob (ie rm ./*ou rm -- *).

Com um glob como *.txt, na verdade, isso não é um problema, pois o ponto é um caractere de opção inválido e causará um erro (até que alguém expanda os utilitários para inventar um significado para ele), mas ainda é mais seguro colocar o ./local de qualquer maneira.


É claro que você também pode obter resultados diferentes para os dois comandos se alterar as opções de brilho do shell ou criar / mover / remover arquivos entre os comandos, mas duvido que você tenha se referido a algum desses casos. (Lidar com arquivos novos / movidos seria extremamente complicado de fazer com segurança.)

ilkkachu
fonte
1
Obrigado. Isso parece destacar um problema com toda a sintaxe da linha de comando: se você nomear arquivos começando com um traço, estará navegando em águas perigosas. Quem sabe quais comandos você pode usar no futuro, muito depois de esquecer os arquivos -.
precisa saber é o seguinte
1
@ Tanner, sim. Em certo sentido, o problema é que os argumentos da linha de comando são apenas strings simples. Se o sistema foi projetado hoje, pode haver mais estrutura para que o programa executado possa saber se um argumento deveria ser um sinalizador de opção ou não. Também existem outros problemas que vêm da estrutura muito frouxa de nomes de arquivos. Há um ensaio muito completo sobre isso feito por dwheeler , mas tenho que avisar que a leitura vai doer. (A partir do detalhe, ou do terror absoluto do que pode dar errado.)
ilkkachu
3
Eu não resisto, você me vendeu, vou ler agora, com lembranças do tempo em que consegui um personagem de retorno de carro no final de muitos nomes de arquivos (tentando ver se eu podia construir um arquivo de lote do Windows que também pode ser executado como um script ... Eu não vi que uma vinda)!
B.Tanner
17

Deixando de lado o comportamento do shell, vamos nos concentrar apenas no que rme lspodemos lidar com eles mesmos. Pelo menos um caso em lsque o que rmnão pode ser removido envolve permissões de diretório e o outro - diretórios especiais .e ...

Permissões de pasta

rmé uma operação em um diretório, porque, ao remover um arquivo, você altera o conteúdo do diretório (ou, em outras palavras, a lista de entradas do diretório, pois o diretório não é mais do que uma lista de nomes de arquivos e inodes ). Isso significa que você precisa de permissões de gravação em um diretório. Mesmo se você for o proprietário do arquivo , sem permissões de diretório, você não poderá remover os arquivos. O inverso também é verdadeiro : rmpode remover arquivos que pertencem a outras pessoas, se você for o proprietário do diretório.

Portanto, você pode muito bem ter permissões de leitura e execução em um diretório, o que permitirá que você percorra o diretório e visualize o conteúdo muito bem, por exemplo ls /bin/echo, mas você não pode, a rm /bin/echomenos que seja o proprietário /bin ou aumente seus privilégios sudo.

E você verá casos como esse em todos os lugares. Aqui está um desses casos: https://superuser.com/a/331124/418028


Diretórios especiais '.' e '..'

Outro caso especial é .e ..diretórios. Se você faz ls .ou ls .., felizmente irá mostrar-lhe o conteúdo, mas rm'ing-los não é permitido:

$ rm -rf .
rm: refusing to remove '.' or '..' directory: skipping '.'
Sergiy Kolodyazhnyy
fonte
15

Se você digitar ls *e rm *, em seguida , é possível remover mais arquivos do que o lsmostrado - eles podem ter sido criados no pequeno intervalo de tempo entre o final lse o início de rm.

choroba
fonte
1
Esse é um caso provável /tmp, onde muitos aplicativos podem criar arquivos temporários, portanto, essa é sempre uma possibilidade nos dois comandos *. No entanto, alguns aplicativos também tornam os arquivos anônimos unlink(), mantendo-os abertos, para que ele apareça, ls *mas rm *não o consiga.
Sergiy Kolodyazhnyy 5/09/19
1
@ OrangeDog Você está perdendo o ponto. Estamos falando de condições de corrida
Sergiy Kolodyazhnyy
1
@OrangeDog Vou precisar verificar isso, já que estou ao telefone, mas o ponto ainda está de pé, mesmo com *a diferença entre o que lsseria exibido e o que rmfunciona, porque a listagem do conteúdo do diretório mudou no meio.
Sergiy Kolodyazhnyy 07/09/19
1
Mesmo se você executar apenas um comando, há uma condição de corrida entre expandir os argumentos e excluí-los.
Pare de prejudicar Monica
1
@OrangeDog Posso concordar com isso. Como a expansão de curinga funciona nos nomes de arquivos existentes, sim, há uma condição de corrida entre o shell expandindo o curinga e um comando para processá-lo; portanto, ls *poderia mostrar o nome do arquivo que já se foi.
Sergiy Kolodyazhnyy
9

ls *e rm *não somos responsáveis ​​por expandir a glob - isso é feito pelo shell antes de passá-lo ao comando.

Isso significa que você pode usar qualquer comando com a lista de arquivos expandida - então eu usaria algo que faça o mínimo possível.

Portanto, uma maneira melhor de fazer isso (ou pelo menos de outra maneira) é pular o intermediário.

echo *mostrará exatamente o que seria passado ao seu rmcomando.

Sombra
fonte
2
Obrigado, eu gosto da ideia de usar eco em vez de ls neste cenário.
precisa saber é o seguinte
3
Ou printf "%s\n" *para obter uma visão inequívoca dos nomes de arquivos com espaços. (Ou %q, em vez de lidar com novas linhas e caracteres de controle também, à custa de mais feio de saída.)
ilkkachu
4

E se:

$ mkdir what
$ cd what
$ mkdir -p huh/uhm ./-r
$ ls *
uhm
$ rm *
$ ls
-r
$ ls -R
.:
-r

./-r:

Basicamente, os curingas que se expandem para itens iniciados por -(ou itens inseridos manualmente, iniciados com -mas que se parecem um pouco com trapaça) podem ser interpretados de forma diferente por lse rm.


fonte
4

Não são casos extremos onde o que lsmostra não é o que rmremove. Um argumento bastante extremo, mas felizmente benigno, é se o argumento que você passa é um link simbólico para um diretório: lsmostrará todos os arquivos no diretório com link simbólico, enquanto rmremoverá o link simbólico, deixando o diretório original e seu conteúdo intocado:

% ln -s $HOME some_link
% ls some_link    # Will display directory contents  
bin    lib    Desktop ...
% rm some_link
% ls $HOME
bin    lib    Desktop ...
alexis
fonte
1
Hã. ln -s $HOME some_link; ls some_linksaídas some_link@para mim, mas eu tenho lsalias para ls -F. Aparentemente, -Faltera o comportamento para mostrar o link em vez de desreferencia-lo. Não esperava isso.
Marcelm
De fato! Também, ls -lpor exemplo, segmenta o link, não o destino ... deve haver maneiras de inspecionar o próprio link.
alexis
1
Adicionar opções à pergunta abre muitas possibilidades - minha resposta é sobre o comportamento não modificado de lse rm.
alexis
Se você diz ls some_link/,  ls -H some_linkou ls -L some_link, ele irá listar o-linked para o diretório, mesmo se você adicionar -Fou -l. Inversamente (mais ou menos), -ddiz olhar para um diretório em vez de seu conteúdo; comparar ls -l /tmpe ls -ld /tmp.
G-Man diz 'Restabelecer Monica
Claro, você pode adicionar sinalizadores que alteram o comportamento de ls. Você está mostrando basicamente por isso enumerando comportamentos com diferentes bandeiras não vale o incômodo para esta pergunta ...
alexis
4

Se você fizer isso apenas em lsvez de ls -a, sim, rmpoderá remover os arquivos ocultos que você não viu lssem -a.

Exemplo:

De acordo com :

dir_test
├── .test
└── test2

ls dir_test : exibirá apenas test2

ls -A dir_test : exibirá test2 + .test

rm -r dir_test : removerá tudo (.test + test2)

Espero que isso ajude você.

DevHugo
fonte
você pode dar um exemplo? Porque normalmente, rm *não removerá os arquivos de ponto. Se isso acontecer, ls *também os mostrará.
Marcelm
Não, ls *não exiba arquivos ocultos.
DevHugo
Mas sim, é um pouco confuso, adicionei alguns exemplos.
DevHugo
1
ls -airá listar ., .., .teste test2. Você pode alterar seu exemplo para usar ls -A, que lista tudo, exceto . e ..(ou seja, somente .teste test2).
G-Man diz 'Restabelecer Monica
3

Já existem muitas respostas boas, mas quero acrescentar uma visão mais profunda.

Faça a si mesmo a pergunta: Quantos parâmetros são passados ​​para ls, se você escrever

ls *

...? Observe que o lscomando não obtém o *parâmetro as se houver algum arquivo que *possa ser expandido. Em vez disso, o shell primeiro executa um globbing antes de chamar o comando, de modo que o lscomando realmente obtém tantos parâmetros quanto os arquivos correspondentes ao globbing. Para suprimir globbing, cite o parâmetro

Isto é verdade para qualquer comando: echo *vs echo '*'.

Existe um script, chame-o countparams.shpara testar o efeito. Ele informa quantos parâmetros foram passados ​​e os lista.

#!/bin/bash
echo "This script was given $# parameters."
arr=( "$@" )
for ((i=0;i<$#;i++)); do
        echo "Parameter $((i+1)): ${arr[$i]}"
done

Torne executável e execute ./countparams.sh *. Aprenda com sua saída!

rexkogitans
fonte
1

A glob se expandirá da mesma maneira nas duas vezes, se o conteúdo do diretório for o mesmo nesses dois momentos diferentes.


Se você realmente deseja verificar o que será removido, use rm -i *.txt. Ele solicitará separadamente cada arquivo antes (tentando) removê-lo.

Isso garante segurança contra as condições de corrida:
        ls *.txt/ um novo arquivo é criado / rm *.txt
porque você é solicitado a cada arquivo pelo mesmo programa que está fazendo a remoção.


Este é demasiado pesado para o uso normal, e se você apelido rmpara rm -i, você vai encontrar-se usando \rmou rm -fcom bastante frequência. Mas vale a pena mencionar pelo menos que existe uma solução para a condição de corrida. (É até portátil para sistemas não-GNU: POSIX rm(1)especifica a -iopção .)

Outra opção seria uma matriz bash:, to_remove=(*.txt)então peça ao usuário para confirmar (talvez depois de fazer ls -ld -- "${to_remove[@]}"), então rm -- "${to_remove[@]}". Portanto, a expansão glob é feita apenas uma vez e a lista é passada literalmente para rm.

Outra opção praticamente utilizável é o GNU rm -I( página de manual ), que solicita a remoção de mais de 4 itens. (Mas não mostra a lista, apenas o total.) Uso alias rm='rm -I'na minha área de trabalho.

É uma boa salvaguarda contra o retorno dos dedos gordos com um padrão meio digitado que combina demais. Mas usar lsprimeiro é geralmente bom em um diretório que você possui, ou em um sistema de usuário único, e quando não há processos em segundo plano que poderiam criar novos arquivos de forma assíncrona. Para se proteger contra dedilhado gordo, não digite rm -rf /foo/bar/bazda esquerda para a direita. rm -rf /é especial, mas rm -rf /usrnão é! Deixe de fora a -rfpeça ou comece com ela lse adicione-a somente rm -rfdepois de digitar o caminho.

Peter Cordes
fonte