Eu tenho uma string que quero usar como nome de arquivo, então quero remover todos os caracteres que não seriam permitidos nos nomes de arquivos, usando Python.
Prefiro ser rigoroso do que o contrário, então digamos que quero reter apenas letras, dígitos e um pequeno conjunto de outros caracteres, como "_-.() "
. Qual é a solução mais elegante?
O nome do arquivo precisa ser válido em vários sistemas operacionais (Windows, Linux e Mac OS) - é um arquivo MP3 na minha biblioteca com o título da música como nome do arquivo e é compartilhado e copiado entre três máquinas.
os.path
fato carrega uma biblioteca diferente, dependendo do sistema operacional (consulte a segunda nota na documentação ). Portanto, se uma função de citação foi implementada,os.path
ela só poderia citar a sequência de caracteres para segurança POSIX ao executar em um sistema POSIX ou para segurança para janelas ao executar no Windows. O nome do arquivo resultante não seria necessariamente válido nas janelas e no POSIX, que é o que a pergunta pede.Respostas:
Você pode olhar para o estrutura do Django para saber como eles criam uma "lesma" a partir de texto arbitrário. Uma lesma é compatível com URL e nome de arquivo.
Os utilitários de texto do Django definem uma função,
slugify()
que provavelmente é o padrão-ouro para esse tipo de coisa. Essencialmente, o código deles é o seguinte.Há mais, mas deixei de fora, já que não trata de slugification, mas de escapar.
fonte
value
. Se o valor deve ser Unicode, você deve ter certeza de que é realmente Unicode. Ou. Você pode deixar de fora a normalização unicode se o seu valor real for realmente uma sequência ASCII.slugify
função foi movida para django / utils / text.py , e esse arquivo também contém umaget_valid_filename
função.Essa abordagem da lista de permissões (ou seja, permitir apenas os caracteres presentes em valid_chars) funcionará se não houver limites na formatação dos arquivos ou na combinação de caracteres válidos que são ilegais (como ".."), por exemplo, o que você diz permitiria um nome de arquivo chamado ". txt" que eu acho que não é válido no Windows. Como esta é a abordagem mais simples, eu tentaria remover o espaço em branco dos valid_chars e acrescentar uma sequência válida conhecida em caso de erro, qualquer outra abordagem precisará saber sobre o que é permitido lidar com as limitações de nomeação de arquivos do Windows e, portanto, ser muito mais complexo.
fonte
valid_chars = frozenset(valid_chars)
não machucaria. É 1,5 vezes mais rápido se aplicado a allchars."CON"
no Windows vai trazer-lhe problemas ...Você pode usar a compreensão da lista junto com os métodos de string.
fonte
filename = "".join(i for i in s if i not in "\/:*?<>|")
"".join( x for x in s if (x.isalnum() or x in "._- "))
Qual é o motivo para usar as strings como nomes de arquivo? Se a legibilidade humana não for um fator, eu usaria o módulo base64, que pode produzir seqüências seguras do sistema de arquivos. Não será legível, mas você não terá que lidar com colisões e é reversível.
Atualização : Alterada com base no comentário de Matthew.
fonte
your_string
precisa ser uma matriz de bytes ou o resultadoencode('ascii')
para que isso funcione.def url2filename(url): url = url.encode('UTF-8') return base64.urlsafe_b64encode(url).decode('UTF-8') def filename2url(f): return base64.urlsafe_b64decode(f).decode('UTF-8')
Só para complicar ainda mais as coisas, não é garantido que você obtenha um nome de arquivo válido apenas removendo caracteres inválidos. Como os caracteres permitidos diferem em nomes de arquivos diferentes, uma abordagem conservadora pode acabar transformando um nome válido em um nome inválido. Você pode adicionar um tratamento especial para os casos em que:
A sequência é composta por todos os caracteres inválidos (deixando uma sequência vazia)
Você acaba com uma string com um significado especial, por exemplo, "." ou ".."
No Windows, determinados nomes de dispositivos são reservados. Por exemplo, você não pode criar um arquivo chamado "nul", "nul.txt" (ou nul.anything, de fato). Os nomes reservados são:
CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8 e LPT9
Provavelmente, você pode solucionar esses problemas anexando alguma sequência aos nomes de arquivos que nunca podem resultar em um desses casos e eliminando caracteres inválidos.
fonte
Existe um bom projeto no Github chamado python-slugify :
Instalar:
Então use:
fonte
test.txt
fica otest-txt
que é demais.Assim como S.Lott respondeu, você pode olhar para o Django Framework para saber como eles convertem uma string em um nome de arquivo válido.
A versão mais recente e atualizada é encontrada em utils / text.py e define "get_valid_filename", que é o seguinte:
(Consulte https://github.com/django/django/blob/master/django/utils/text.py )
fonte
django.utils.text import get_valid_filename
re.sub(r'(?u)[^-\w.]', '', s)
remove todos os caracteres que não são letras, nem números (0-9), nem o sublinhado ('_'), nem o traço ('-'), nem o ponto ('.' ) "Letras" aqui inclui todas as letras unicode, como 漢語.Esta é a solução que eu finalmente usei:
A chamada unicodedata.normalize substitui caracteres acentuados pelo equivalente não acentuado, o que é melhor do que simplesmente removê-los. Depois disso, todos os caracteres não permitidos são removidos.
Minha solução não anexa uma sequência conhecida para evitar possíveis nomes de arquivos não permitidos, porque eu sei que eles não podem ocorrer devido ao meu formato de nome de arquivo específico. Uma solução mais geral precisaria fazê-lo.
fonte
Lembre-se de que não há restrições sobre nomes de arquivos em sistemas Unix além de
Tudo o resto é jogo justo.
Sim, eu apenas armazenei códigos de cores ANSI em um nome de arquivo e os fiz efeito.
Para entretenimento, coloque um caractere BEL em um nome de diretório e assista à diversão que se segue quando você faz o CD nele;)
fonte
Em uma linha:
você também pode colocar o caractere '_' para torná-lo mais legível (no caso de substituir barras, por exemplo)
fonte
Você pode usar o método re.sub () para substituir qualquer coisa que não seja "semelhante a um arquivo". Mas, na verdade, todo personagem pode ser válido; então não há funções pré-construídas (acredito), para fazê-lo.
Resultaria em uma manipulação de arquivo para /tmp/filename.txt.
fonte
Ele não lida com strings vazias, nomes de arquivos especiais ('nul', 'con' etc.).
fonte
Embora você tenha que ter cuidado. Não está claramente dito em sua introdução, se você estiver olhando apenas para o idioma latino. Algumas palavras podem se tornar sem sentido ou outro significado se você as higienizar apenas com caracteres ascii.
imagine que você tem "forêt poésie" (poesia florestal), sua sanitização pode dar "fort-posie" (forte + algo sem sentido)
Pior se você tiver que lidar com caracteres chineses.
"下 北 沢" seu sistema pode acabar fazendo "---", que está fadado a falhar depois de um tempo e não é muito útil. Portanto, se você lida apenas com arquivos, eu os incentivaria a chamá-los de uma cadeia genérica que você controla ou a manter os caracteres como são. Para URIs, aproximadamente o mesmo.
fonte
Por que não apenas envolver o "osopen" com uma tentativa / exceção e deixar o sistema operacional subjacente determinar se o arquivo é válido?
Isso parece muito menos trabalho e é válido, independentemente do sistema operacional que você usa.
fonte
osopen
execução em uma máquina.Outro problema que os outros comentários ainda não abordaram é a string vazia, que obviamente não é um nome de arquivo válido. Você também pode acabar com uma string vazia, eliminando muitos caracteres.
O que com os nomes de arquivos reservados do Windows e os problemas com pontos, a resposta mais segura para a pergunta "como normalizar um nome de arquivo válido a partir de entradas arbitrárias do usuário?" é “nem se preocupe em tentar”: se você encontrar outra maneira de evitá-lo (por exemplo, usando chaves primárias inteiras de um banco de dados como nomes de arquivos), faça isso.
Se você precisar, e realmente precisa permitir espaços e '.' para extensões de arquivo como parte do nome, tente algo como:
Mesmo isso não pode ser garantido, especialmente em sistemas operacionais inesperados - por exemplo, o RISC OS odeia espaços e usa '.' como um separador de diretório.
fonte
Gostei da abordagem python-slugify aqui, mas ela também estava removendo pontos, o que não era desejado. Então, eu o otimizei para enviar um nome de arquivo limpo para o s3 desta maneira:
Código de exemplo:
Resultado:
Isso é à prova de falhas, funciona com nomes de arquivos sem extensão e funciona apenas para nomes de arquivos de caracteres não seguros (o resultado está
none
aqui).fonte
Resposta modificada para python 3.6
fonte
Sei que há muitas respostas, mas elas se baseiam principalmente em expressões regulares ou módulos externos, então eu gostaria de dar minha própria resposta. Uma função python pura, nenhum módulo externo necessário, nenhuma expressão regular usada. Minha abordagem não é limpar caracteres inválidos, mas apenas permitir caracteres válidos.
se desejar, você pode adicionar seus próprios caracteres válidos à
validchars
variável no início, como as letras nacionais que não existem no alfabeto inglês. Isso é algo que você pode ou não querer: alguns sistemas de arquivos que não são executados no UTF-8 ainda podem ter problemas com caracteres não-ASCII.Esta função é para testar a validade de um único arquivo, portanto substituirá os separadores de caminho por _ considerando-os como caracteres inválidos. Se você quiser adicionar isso, é trivial modificar o
if
para incluir o separador de caminho os.fonte
A maioria dessas soluções não funciona.
'/ olá / mundo' -> 'helloworld'
'/ helloworld' / -> 'helloworld'
Isso não é o que você deseja em geral, digamos que você esteja salvando o html para cada link, você substituirá o html por uma página da web diferente.
Eu escolho um ditado como:
2 representa o número que deve ser anexado ao próximo nome do arquivo.
Eu procuro o nome do arquivo toda vez que ditar. Se não estiver lá, eu crio um novo, acrescentando o número máximo, se necessário.
fonte
Não é exatamente o que o OP estava pedindo, mas é isso que eu uso, porque preciso de conversões únicas e reversíveis:
O resultado é "um pouco" legível, pelo menos do ponto de vista do administrador de sistemas.
fonte
def safe_filename(filename): return safePath(filename.strip().replace(' ','_'))
Se você não se importa em instalar um pacote, isso deve ser útil: https://pypi.org/project/pathvalidate/
Em https://pypi.org/project/pathvalidate/#sanitize-a-filename :
fonte
Tenho certeza de que essa não é uma ótima resposta, pois modifica a string em que está sendo repetida, mas parece funcionar bem:
fonte
"".join( x for x in s if (x.isalnum() or x in "._- "))
neste postATUALIZAR
Todos os links quebrados além do reparo nesta resposta de 6 anos.
Além disso, eu também não faria mais dessa maneira, apenas
base64
codificaria ou soltaria caracteres não seguros. Exemplo de Python 3:Com
base64
você pode codificar e decodificar, para recuperar o nome do arquivo original novamente.Mas, dependendo do caso de uso, é melhor gerar um nome de arquivo aleatório e armazenar os metadados em um arquivo ou banco de dados separado.
RESPOSTA ORIGINAL LINKROTTEN :
O
bobcat
projeto contém um módulo python que faz exatamente isso.Não é completamente robusto, veja este post e esta resposta .
Portanto, como observado: a
base64
codificação é provavelmente uma idéia melhor se a legibilidade não for importante.fonte