Como passar string com espaços em branco à direita para o subprocesso de python

1

Gostaria de renomear em lote os arquivos que têm espaços à direita para o mesmo nome sem os espaços. No python 3.6.5, o seguinte funciona bem:

subprocess.call("mv '%s' '%s'"%(name,name.strip()),shell=True)

No entanto, no python 2.7, recebo erros como "arquivo não encontrado". Existe uma maneira de realizar o que eu quero no python 2.7?

Atualização: aqui está o código

for root, dirnames, filenames in os.walk('.'):
   for name in fnmatch.filter(filenames, "randconf*"):     
          if " " in name: 
             subprocess.call('mv "%s" "%s"'%name,name.strip()),shell=True)

Se eu substituir a linha de subprocesso por "print name", recebo, por exemplo:

randconf_1                                                      
randconf_10                                                     
randconf_11                                                     
randconf_12                                                     
randconf_13                                                     
randconf_14                                                     
randconf_15
    
por painfulenglish 21.09.2018 / 17:15

1 resposta

2

Aqui, como apontado por @jordanm, seu problema é que você está chamando mv no nome dos arquivos para cada diretório percorrido por os.walk , mas os.walk não altera o diretório de trabalho atual.

Portanto, para os arquivos encontrados em subdiretórios, isso não funcionará.

Você precisaria passar o caminho completo do arquivo para mv , então algo como os.path.join(dirpath, name) .

O ideal seria que o alterasse os diretórios à medida que ele progride como perl File::Find finddepth() ou BSD / GNU find -execdir do, o que tornaria mais seguro e evitar problemas com árvores de diretório muito profundas, mas não acho que você possa fazer isso facilmente com python ' os.walk() .

Agora, há alguns outros problemas com seu código:

Vulnerabilidade de injeção de comando

Agora, espaços à direita são a menor das suas preocupações:

subprocess.call("mv '%s' '%s'"%(name,name.strip()),shell=True)

Isso é basicamente uma vulnerabilidade de injeção de comando (pense, por exemplo, em um arquivo chamado '$(reboot)' (com as aspas)).

Como regra, não incorpore texto arbitrário em strings interpretadas como código de shell (ou código em qualquer idioma que possa causar algum dano).

Com seu código (a variante que usa o formulário 'mv "%s" "%s"' ), você pode ter o mesmo erro com arquivos chamados randconf $x ou randconf $(test) , por exemplo.

Aqui, use:

subprocess.call(("mv", "--", name, name.strip()),shell=False)

Se você tiver que usar um shell, uma maneira melhor de passar dados para esse shell é usar variáveis de ambiente:

os.putenv("OLD", name)
os.putenv("NEW", name.strip())
subprocess.call('mv -- "$OLD" "$NEW"',shell=True)

A execução de uma shell também é cara. Especialmente em sistemas em que sh é, na verdade, um grande shell completo como bash , ksh93 ou zsh que geralmente leva um tempo significativo para carregar e inicializar.

Enquanto você está chamando o shell, é melhor fazer todo o processo e renomear o código do shell.

Ambiguidade de mv

mv não é um comando com a melhor interface ( cp e ln têm os mesmos problemas). O problema é que mv faz muitas coisas diferentes, mas não com base no que / como você pede, mas no contexto.

mv A B

Ou

  • renomeia A para B / A se B existir e for do tipo diretório ou symlink para o diretório no mesmo sistema de arquivos
  • faz o mesmo, mas com uma cópia (preservando o máximo de atributos possíveis), seguido por delete se a renomeação cruzar um limite do sistema de arquivos
  • faz uma renomeação de outra forma (excluindo o destino se ele existia de antemão)

Aqui, você quer apenas uma chamada básica do sistema rename() , ou melhor, um rename() que não atrapalha um arquivo já existente (como o Linux ' renameat2(... RENAME_NOREPLACE) ), que também alivia problemas como "randconf_1 " e "randconf_1 " sendo renomeados para randonf_1 .

Com o GNU mv , você pode fazer isso com:

mv -nT -- "$old" "$new"

mas isso não é portátil. (Observe também que o mv do GNU não usa renameat2() no Linux, então tem uma condição de corrida (menor aqui))

Em qualquer caso, em python , você não precisa chamar um programa separado apenas para renomear um arquivo.

os.rename(name, name.strip());

(não sei que python tem uma ligação com o Linux ' renameat2() )

space vs whitespace

python ' strip() retira os caracteres iniciais e finais espaço em branco . Aqui todas as strings começam com randconf , então é o mesmo que rstrip() . espaço em branco inclui o caractere de espaço ASCII, mas todos os tipos de outros caracteres de espaçamento vertical e horizontal (apenas os ASCII pareceriam), como TAB, LF, CR ...

Como você está procurando arquivos que contenham caracteres de espaço em qualquer lugar da linha, você pode acabar não renomeando alguns nomes de arquivos que terminam com espaço em branco (como "randconf_\t" que não contém espaço) ou chame mv para nomes de arquivos que não terminam em espaço em branco (como "randconf_x y" ).

Você poderia usar fnmatch.filter(filenames, "randconf* ") e rstrip(" ") se apenas se preocupasse com caracteres de espaço à direita

Equivalente ao shell POSIX:

Faça isso com a sintaxe do shell e do utilitário POSIX:

find . -depth -name 'randconf*[[:space:]]' ! -type d -exec sh -c '
  for file do
    newfile=${file%"${file##*[![:space:]]}"}
    [ -e "$newfile" ] || [ -L "$newfile" ] || mv -- "$file" "$newfile"
  done' sh {} +

Ou ligeiramente mais confiável com os utilitários GNU

find . -depth -name 'randconf*[[:space:]]' ! -type d -execdir bash -O extglob -c '
  for file do
    newfile=${file%*([[:space:]])}
    mv -nT -- "$file" "$newfile"
  done' sh {} +

(note que ele remove todos os caracteres considerados como espaço em branco na localidade atual, não apenas os caracteres ASCII como em python; altere a localidade para C para corresponder apenas ao espaço em branco ASCII).

    
por 21.09.2018 / 17:56

Tags