Renomear retorna "palavra de barra não permitida" ao tentar minúsculas partes de vários nomes de arquivos

12

Eu tenho dois arquivos em uma pasta no meu Ubuntu 16.04:

a1.dat
b1.DAT

Quero renomear b1.DATpara, b1.datportanto, os seguintes arquivos, como resultado, na pasta:

a1.dat
b1.dat

Eu tentei (sem sucesso):

$ rename *.DAT *.dat
Bareword "b1" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

e

$ find . -iname "*.DAT" -exec rename DAT dat '{}' \;
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

A pesquisa não resultou em nenhuma solução significativa ...

Samo
fonte
Você também pode querer aprender sobre mmv
PlasmaHH 17/03

Respostas:

14

Esse erro parece que vem do Perl rename. Você precisa usar a citação, mas precisa especificar apenas a parte que deseja alterar, o estilo de pesquisar e substituir. A sintaxe é assim:

rename -n 's/\.DAT/\.dat/' *

Remova -napós o teste para renomear os arquivos.

Para incluir arquivos ocultos, altere a configuração da glob antes de executar o comando:

shopt -s dotglob

Se quiser renomear arquivos recursivamente, você pode usar

shopt -s globstar
rename -n 's/\.DAT/\.dat/' **

ou se houver muitos caminhos dentro ou abaixo do diretório atual que não terminam .DAT, é melhor especificar esses caminhos no segundo comando:

rename -n 's/\.DAT/\.dat/' **/*.DAT

Isso será mais rápido se seus arquivos tiverem vários nomes diferentes que não terminem .DAT. [1]

Para desativar essas configurações, você pode usar shopt -u, por exemplo shopt -u globstar, mas elas estão desativadas por padrão e serão desativadas quando você abrir um novo shell.

Se isso resultar em uma lista de argumentos excessivamente longa, você pode usar, por exemplo find:

find -type f -name "*.DAT" -exec rename -n -- 's/\.DAT/\.dat/' {} \;

ou melhor

find -type f -name "*.DAT" -exec rename -n -- 's/\.DAT/\.dat/' {} +

Usar find ... -execcom +é mais rápido do que usar, \;pois cria uma lista de argumentos a partir dos arquivos encontrados. Originalmente, pensei que não seria possível usá-lo, porque você mencionou que estava tendo um argument list too longproblema, mas agora sei que a lista também será dividida de maneira inteligente em várias invocações do comando, conforme necessário, para evitar esse problema. [2] .

Como renameprocessará todos os nomes de arquivos da mesma maneira, não importa quanto tempo a lista de argumentos tenha, pois ela pode ser dividida com segurança em várias invocações. Se o comando que você está usando -execnão aceitar vários argumentos ou exigir que seus argumentos estejam em uma ordem específica ou por qualquer outro motivo, a divisão da lista de argumentos fará com que algo indesejado aconteça, você poderá usar \;, o que fará com que o comando seja chamado uma vez para cada arquivo encontrado (se a lista de argumentos for muito longa para outros métodos, isso levará muito tempo!).


Muito obrigado a Eliah Kagan pelas sugestões muito úteis para melhorar esta resposta:

[1] Especificando os nomes dos arquivos ao globbing .
[2] find ... -execcom +divide a lista de argumentos .

Zanna
fonte
Obrigado. Como fazer isso recursivamente (para todos os arquivos em todas as subpastas)?
Samo
@Samo see my edit :)
Zanna
Não está funcionando: $ rename 's / \. DAT / \. Dat /' ** bash: / usr / bin / rename: Lista de argumentos muito longa
Samo
1
@Samo você precisa remover a -npartir de renomeação depois de testar - é apenas para mostrar o que será alterado
Zanna
1
No Centos e no Arch, renomear não exibe esse comportamento. Eu cheguei aqui usando o Debian na WSL. Essa sintaxe funcionou.
xtian 2/03
6

Você pode fazer:

rename -n 's/DAT$/\L$&/' *.DAT

Descarte -npara que a renomeação real ocorra.

  • o padrão glob *.DATcorresponde a todos os arquivos que terminam no .DATdiretório atual

  • na renamesubstituição, DAT$combina DATno final

  • \L$&torna a partida inteira em minúsculas; $&refere-se a toda a partida

Se você apenas quer fazer por b1.DAT:

rename -n 's/DAT$/\L$&/' b1.DAT

Exemplo:

% rename -n 's/DAT$/\L$&/' *.DAT
rename(b1.DAT, b1.dat)
heemail
fonte
4

Outras respostas abordaram um dos dois principais aspectos desta pergunta: como executar com êxito a operação de renomeação necessária. O objetivo desta resposta é explicar por que seus comandos não funcionaram, incluindo o significado dessa estranha mensagem de erro "palavra de barra não permitida" no contexto do renamecomando.

A primeira seção desta resposta é sobre o relacionamento entre renamee Perl e como renameusa o primeiro argumento da linha de comando que você passa, que é o seu argumento de código. A segunda seção é sobre como o shell executa expansões - especificamente globbing - para construir uma lista de argumentos. A terceira seção é sobre o que está acontecendo no código Perl que fornece erros "Bareword not allowed". Finalmente, a quarta seção é um resumo de todas as etapas que ocorrem entre inserir o comando e obter o erro.

1. Quando renamereceber mensagens de erro estranhas, adicione "Perl" à sua pesquisa.

No Debian e no Ubuntu, o renamecomando é um script Perl que executa a renomeação de arquivos. Em versões mais antigas - incluindo 14.04 LTS, que ainda é suportado até o momento -, era um link simbólico apontando ( indiretamente ) para o prenamecomando. Nas versões mais recentes, ele aponta para o file-renamecomando mais recente . Esses dois comandos de renomeação de Perl funcionam da mesma maneira, e vou apenas me referir a ambos como renamepara o restante desta resposta.

Quando você usa o renamecomando, você não está apenas executando o código Perl que outra pessoa escreveu. Você também está escrevendo seu próprio código Perl e dizendo renamepara executá-lo. Isso ocorre porque o primeiro argumento de linha de comando que você passar para o renamecomando, com excepção argumentos opcionais como -n, consiste de código Perl real . O renamecomando usa esse código para operar em cada um dos nomes de caminho que você passa como argumentos de linha de comando subsequentes. (Se você não passar nenhum argumento de nome de caminho, então renamelê nomes de caminho da entrada padrão , um por linha.)

O código é executado dentro de um loop , que itera uma vez por nome de caminho. Na parte superior de cada iteração do loop, antes da execução do código, a $_variável especial recebe o nome do caminho que está sendo processado no momento. Se seu código fizer com que o valor $_seja alterado para outra coisa, esse arquivo será renomeado para ter o novo nome.

Muitas expressões em Perl operam implicitamente na $_variável quando não recebem outra expressão para usar como operando . Por exemplo, a expressão de substituição $str =~ s/foo/bar/altera a primeira ocorrência de foona cadeia mantida pela $str variável para barou a deixa inalterada se não contiver foo. Se você acabou de escrever s/foo/bar/sem usar explicitamente o =~operador , ele continuará funcionando $_. Isso quer dizer que s/foo/bar/é a abreviação de $_ =~ s/foo/bar/.

É comum passar uma s///expressão de renamecomo o argumento de código (ou seja, o primeiro argumento de linha de comando), mas você não tem que. Você pode fornecer qualquer código Perl que você deseja que seja executado dentro do loop, para examinar cada valor $_e (condicionalmente) modificá-lo.

Isso tem muitas consequências interessantes e úteis , mas a grande maioria delas está muito além do escopo desta pergunta e resposta. A principal razão pela qual eu trouxe isso aqui - de fato, a principal razão pela qual decidi postar esta resposta - é enfatizar que, porque o primeiro argumento a renameser realmente o código Perl, sempre que você receber uma mensagem de erro estranha e você Se tiver problemas para encontrar informações sobre isso pesquisando, você pode adicionar "Perl" à string de pesquisa (ou até mesmo substituir "renomear" por "Perl", às vezes) e geralmente encontrará uma resposta.

2. Com rename *.DAT *.dat, o renamecomando nunca viu *.DAT!

Um comando como rename s/foo/bar/ *.txtnormalmente não passa *.txtcomo um argumento de linha de comando para o renameprograma, e você não deseja, a menos que tenha um arquivo cujo nome seja literalmente *.txt, o que espero que não seja.

renamenão interpreta padrões glob como *.txt, *.DAT, *.dat, x*, *y, ou *quando passado para ele como argumentos pathname. Em vez disso, seu shell realiza a expansão do nome do caminho neles (que também é chamada de expansão de nome de arquivo e também chamada globbing). Isso acontece antes que o renameutilitário seja executado. O shell expande os globs para nomes de caminho potencialmente múltiplos e passa todos eles, como argumentos separados da linha de comando, para rename. No Ubuntu, seu shell interativo é o Bash , a menos que você o tenha alterado, e é por isso que vinculei o manual de referência do Bash acima.

Há uma situação em que um padrão glob pode ser passado como um único argumento de linha de comando não expandido para rename: quando ele não corresponde a nenhum arquivo. Cascas diferentes exibem um comportamento padrão diferente nessa situação, mas o comportamento padrão de Bash é simplesmente passar a bola literalmente. No entanto, você raramente quer isso! Se você o desejou, assegure-se de que o padrão não seja expandido, citando -o. Isso vale para passar argumentos para qualquer comando, não apenas para rename.

A citação não é apenas para globbing (expansão de nome de arquivo), porque há outras expansões que seu shell executa em texto não citado e, para algumas delas, mas não outras , também em texto entre " "aspas. Em geral, sempre que você quiser passar um argumento que contenha caracteres que possam ser tratados especialmente pelo shell, incluindo espaços, você deve citá-lo, de preferência com ' 'aspas .

O código Perl s/foo/bar/não contém nada tratado especialmente pelo shell, mas seria uma boa idéia para mim ter citado isso também - e escrito 's/foo/bar/'. (Na verdade, a única razão que eu fiz não foi que seria confuso para alguns leitores, como eu ainda não tinha falado sobre citando.) A razão de eu dizer isso teria sido bem é porque é muito comum que o código Perl não contêm esses caracteres e, se eu mudar esse código, talvez não me lembre de verificar se era necessário citar. Por outro lado, se você deseja que o shell expanda um globo, ele não deve ser citado.

3. O que o intérprete Perl quer dizer com "palavra de barra não permitida"

As mensagens de erro que você mostrou na sua pergunta revelam que, quando você executava rename *.DAT *.dat, seu shell se expandia *.DATpara uma lista de um ou mais nomes de arquivos e que o primeiro era b1.DAT. Todos os argumentos subseqüentes - quaisquer outros expandidos *.DATe expandidos de - *.datvieram após esse argumento, portanto, eles teriam sido interpretados como nomes de caminho.

Como o que realmente foi executado era algo como rename b1.DAT ..., e porque renametrata seu primeiro argumento de não opção como código Perl, a pergunta é: por que b1.DATesses erros de "palavra de barra não são permitidos" são produzidos quando você o executa como código Perl?

Bareword "b1" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

Em um shell, citamos nossas strings para protegê-las de expansões não intencionais do shell que, de outra forma, as transformariam em outras strings automaticamente (consulte a seção acima). Os shells são linguagens de programação para fins especiais que funcionam de maneira muito diferente das linguagens de uso geral (e sua sintaxe e semântica muito estranhas refletem isso). Mas o Perl é uma linguagem de programação de uso geral e, como a maioria das linguagens de programação de uso geral, o principal objetivo de citar no Perl não é proteger as strings, mas mencioná-las. Essa é realmente a maneira pela qual a maioria das linguagens de programação é semelhante à linguagem natural. Em inglês, e supondo que você tenha um cachorro, "seu cachorro" é uma frase de duas palavras, enquanto seu cachorro é um cachorro. Da mesma forma, em Perl, '$foo'é uma string, enquanto $fooé algo cujo nome é $foo.

No entanto, diferentemente de qualquer outra linguagem de programação de uso geral, o Perl também às vezes interpreta o texto não citado como mencionando uma string - uma string que é a "mesma" que ela, no sentido de que é composta dos mesmos caracteres em a mesma ordem. Ele só tentará interpretar o código dessa maneira se for uma palavra de barra (nenhum $ou outro sigilo, veja abaixo) e depois não encontrar outro significado para fornecê-lo. Em seguida, ele será interpretado como uma sequência, a menos que você o solicite, ativando as restrições .

As variáveis ​​no Perl geralmente começam com um caractere de pontuação chamado sigil , que especifica o tipo amplo da variável. Por exemplo, $significa escalar , @meios de matriz , e %meios de hash . ( Existem outros. ) Não se preocupe se você achar isso confuso (ou chato), porque só estou dizendo que quando um nome válido aparece em um programa Perl, mas não é precedido por um sigilo, esse nome é dito ser uma palavra de baralho .

As palavras de barra servem a vários propósitos, mas geralmente significam uma função interna ou uma sub-rotina definida pelo usuário que foi definida no programa (ou em um módulo usado pelo programa). O Perl não possui funções internas chamadas b1ou DAT, portanto, quando o interpretador Perl vê o código b1.DAT, ele tenta tratar b1e DATcomo os nomes das sub-rotinas. Assumindo que nenhuma sub-rotina foi definida, isso falha. Em seguida, desde que as restrições não tenham sido ativadas, elas serão tratadas como cadeias. Isso funcionará, embora você realmente pretenda que isso aconteça seja uma incógnita. O .operador do Perl concatena as strings , portanto, b1.DATavalia para a stringb1DAT. Ou seja, b1.DATé uma maneira ruim de escrever algo como 'b1' . 'DAT'ou "b1" . "DAT".

Você pode testar isso sozinho executando o comando perl -E 'say b1.DAT', que passa o script Perl curto say b1.DATpara o interpretador Perl, que o executa, imprimindo b1DAT. (Nesse comando, as ' 'cotações de dizer ao shell para passar say b1.DATcomo um único argumento de linha de comando, caso contrário, o espaço poderia causar saye b1.DATdeve ser analisado como palavras separadas e perliria recebê-los como argumentos separados. perlSe não se vêem as aspas, como o shell os remove .)

Mas agora, tente escreveruse strict; no script Perl antes say. Agora ele falha com o mesmo tipo de erro que você obteve rename:

$ perl -E 'use strict; say b1.DAT'
Bareword "b1" not allowed while "strict subs" in use at -e line 1.
Bareword "DAT" not allowed while "strict subs" in use at -e line 1.
Execution of -e aborted due to compilation errors.

Isso aconteceu porque use strict;proibiu o intérprete Perl de tratar palavras de baralho como seqüências de caracteres. Para proibir esse recurso em particular, seria realmente suficiente permitir apenas a subsrestrição. Este comando produz os mesmos erros que acima:

perl -E 'use strict "subs"; say b1.DAT'

Mas geralmente os programadores Perl apenas escrevem use strict;, o que habilita a subsrestrição e mais duas. use strict;é geralmente uma prática recomendada. Portanto, o renamecomando faz isso para o seu código. É por isso que você recebe essa mensagem de erro.

4. Em resumo, foi o que aconteceu:

  1. Seu shell passou b1.DATcomo o primeiro argumento da linha de comando, que renametratou como código Perl para executar em um loop para cada argumento do nome do caminho.
  2. Isso foi considerado como significante b1e DATconectado ao .operador.
  3. b1e DATnão eram prefixados com sigilos, portanto eram tratados como palavras de baralho.
  4. Essas duas palavras de barra teriam sido tomadas como nomes de funções internas ou quaisquer sub-rotinas definidas pelo usuário, mas nenhuma dessas coisas tinha nenhum desses nomes.
  5. Se os "subs estritos" não tivessem sido ativados, eles teriam sido tratados como as expressões de string 'b1'e 'DAT'e concatenados. Isso está longe do que você pretendia, o que ilustra como esse recurso geralmente não é útil.
  6. Mas "subs rigorosas" foi habilitado, porque renamepermite que todas as restrições ( vars, refs, e subs). Portanto, você recebeu um erro.
  7. renamesair devido a este erro. Como esse tipo de erro ocorreu mais cedo, nenhuma tentativa de renomeação de arquivo foi feita, mesmo que você não tenha passado -n. Isso é bom, pois geralmente protege os usuários contra alterações não intencionais do nome do arquivo e, ocasionalmente, até contra a perda real de dados.

Agradeço a Zanna , que me ajudou a abordar várias deficiências importantes em um rascunho mais detalhado desta resposta . Sem ela, essa resposta teria feito muito menos sentido e talvez não fosse divulgada.

Eliah Kagan
fonte