Pasta recursiva Python lida


225

Eu tenho um plano de fundo em C ++ / Obj-C e estou apenas descobrindo o Python (estou escrevendo há cerca de uma hora). Estou escrevendo um script para ler recursivamente o conteúdo de arquivos de texto em uma estrutura de pastas.

O problema que tenho é o código que escrevi funcionará apenas para uma pasta em profundidade. Eu posso ver por que, no código (veja #hardcoded path), simplesmente não sei como posso avançar com o Python, pois minha experiência com ele é apenas nova.

Código Python:

import os
import sys

rootdir = sys.argv[1]

for root, subFolders, files in os.walk(rootdir):

    for folder in subFolders:
        outfileName = rootdir + "/" + folder + "/py-outfile.txt" # hardcoded path
        folderOut = open( outfileName, 'w' )
        print "outfileName is " + outfileName

        for file in files:
            filePath = rootdir + '/' + file
            f = open( filePath, 'r' )
            toWrite = f.read()
            print "Writing '" + toWrite + "' to" + filePath
            folderOut.write( toWrite )
            f.close()

        folderOut.close()

Respostas:


347

Certifique-se de entender os três valores de retorno de os.walk:

for root, subdirs, files in os.walk(rootdir):

tem o seguinte significado:

  • root: Caminho atual "percorrido"
  • subdirs: Arquivos no rootdiretório do tipo
  • files: Arquivos em root(não em subdirs) do tipo que não seja diretório

E use em os.path.joinvez de concatenar com uma barra! O seu problema é filePath = rootdir + '/' + file- você deve concatenar a pasta "caminhada" atualmente, em vez da pasta superior. Então isso deve ser filePath = os.path.join(root, file). BTW "arquivo" é incorporado, portanto você normalmente não o usa como nome de variável.

Outro problema são seus loops, que devem ser assim, por exemplo:

import os
import sys

walk_dir = sys.argv[1]

print('walk_dir = ' + walk_dir)

# If your current working directory may change during script execution, it's recommended to
# immediately convert program arguments to an absolute path. Then the variable root below will
# be an absolute path as well. Example:
# walk_dir = os.path.abspath(walk_dir)
print('walk_dir (absolute) = ' + os.path.abspath(walk_dir))

for root, subdirs, files in os.walk(walk_dir):
    print('--\nroot = ' + root)
    list_file_path = os.path.join(root, 'my-directory-list.txt')
    print('list_file_path = ' + list_file_path)

    with open(list_file_path, 'wb') as list_file:
        for subdir in subdirs:
            print('\t- subdirectory ' + subdir)

        for filename in files:
            file_path = os.path.join(root, filename)

            print('\t- file %s (full path: %s)' % (filename, file_path))

            with open(file_path, 'rb') as f:
                f_content = f.read()
                list_file.write(('The file %s contains:\n' % filename).encode('utf-8'))
                list_file.write(f_content)
                list_file.write(b'\n')

Se você não sabia, a withinstrução para arquivos é uma abreviação:

with open('filename', 'rb') as f:
    dosomething()

# is effectively the same as

f = open('filename', 'rb')
try:
    dosomething()
finally:
    f.close()

4
Soberbo, muitas impressões para entender o que está acontecendo e funciona perfeitamente. Obrigado! +1
Brock Woolf

16
Cabe a alguém tão burro / inconsciente quanto eu ... esse exemplo de código grava um arquivo txt em cada diretório. Ainda bem que eu testei em uma pasta controlada versão, apesar de tudo o que preciso para escrever um script de limpeza também está aqui :)
Steazy

que segundo (mais longa) trecho de código funcionou muito bem, me salvou um monte de trabalho chato
amphibient

1
Como a velocidade, se é obviamente o aspecto mais importante, os.walknão é ruim, embora eu tenha pensado em uma maneira ainda mais rápida os.scandir. Todas as globsoluções são muito mais lentas que o walk& scandir. Minha função, bem como uma análise completa da velocidade, podem ser encontradas aqui: stackoverflow.com/a/59803793/2441026
user136036 18/01

112

Se você estiver usando o Python 3.5 ou superior, poderá fazer isso em 1 linha.

import glob

for filename in glob.iglob(root_dir + '**/*.txt', recursive=True):
     print(filename)

Conforme mencionado na documentação

Se recursiva for verdadeira, o padrão '**' corresponderá a qualquer arquivo e zero ou mais diretórios e subdiretórios.

Se você quiser todos os arquivos, poderá usar

import glob

for filename in glob.iglob(root_dir + '**/*', recursive=True):
     print(filename)

TypeError: iglob () tem um argumento palavra-chave 'recursiva' inesperado
Jewenile

1
Conforme mencionado no início, que é única para o Python 3.5+
ChillarAnand

9
root_dir deve ter uma barra à direita (caso contrário, você terá algo como 'folder ** / *' em vez de 'folder / ** / *' como o primeiro argumento). Você pode usar os.path.join (root_dir, ' * / '), mas não sei se é aceitável usar os.path.join com caminhos curinga (embora funcione para o meu aplicativo).
Drojf 6/04/19

@ChillarAnand Você pode adicionar um comentário ao código nesta resposta que root_dirprecisa de uma barra final? Isso economizará tempo das pessoas (ou pelo menos teria me economizado tempo). Obrigado.
Dan Nissenbaum

1
Se eu executei isso como na resposta, não funcionou recursivamente. Para fazer este trabalho de forma recursiva eu tive que mudá-lo para: glob.iglob(root_dir + '**/**', recursive=True). Estou trabalhando em Python 3.8.2
mikey

38

Concordo com Dave Webb, os.walkproduzirá um item para cada diretório da árvore. O fato é que você simplesmente não precisa se preocupar subFolders.

Código como este deve funcionar:

import os
import sys

rootdir = sys.argv[1]

for folder, subs, files in os.walk(rootdir):
    with open(os.path.join(folder, 'python-outfile.txt'), 'w') as dest:
        for filename in files:
            with open(os.path.join(folder, filename), 'r') as src:
                dest.write(src.read())

3
Agradável. Isso também funciona. No entanto, prefiro a versão do AndiDog, embora seja mais longa, porque é mais claro entender como iniciante o Python. +1
Brock Woolf

20

TL; DR: é o equivalente a find -type fexaminar todos os arquivos em todas as pastas abaixo e incluindo o atual:

for currentpath, folders, files in os.walk('.'):
    for file in files:
        print(os.path.join(currentpath, file))

Como já mencionado em outras respostas, os.walk()é a resposta, mas poderia ser melhor explicada. É bem simples! Vamos andar por esta árvore:

docs/
└── doc1.odt
pics/
todo.txt

Com este código:

for currentpath, folders, files in os.walk('.'):
    print(currentpath)

A currentpathé a pasta atual para a qual está olhando. Isso produzirá:

.
./docs
./pics

Então ele executa um loop três vezes, porque existem três pastas: a atual,, docse pics. Em cada loop, ele preenche as variáveis folderse filescom todas as pastas e arquivos. Vamos mostrar a eles:

for currentpath, folders, files in os.walk('.'):
    print(currentpath, folders, files)

Isso nos mostra:

# currentpath  folders           files
.              ['pics', 'docs']  ['todo.txt']
./pics         []                []
./docs         []                ['doc1.odt']

Assim, na primeira linha, vemos que estamos em pasta ., que contém duas pastas ou seja, picse docs, e que há um arquivo, ou seja todo.txt. Você não precisa fazer nada para recolocar nessas pastas, porque, como vê, ele se repete automaticamente e apenas fornece os arquivos em todas as subpastas. E qualquer subpasta disso (embora não tenhamos as do exemplo).

Se você deseja apenas percorrer todos os arquivos, o equivalente a find -type f, você pode fazer isso:

for currentpath, folders, files in os.walk('.'):
    for file in files:
        print(os.path.join(currentpath, file))

Isso gera:

./todo.txt
./docs/doc1.odt

9

A pathlibbiblioteca é realmente ótima para trabalhar com arquivos. Você pode fazer um globo recursivo em um Pathobjeto como esse.

from pathlib import Path

for elem in Path('/path/to/my/files').rglob('*.*'):
    print(elem)

6

Se você deseja uma lista simples de todos os caminhos em um determinado diretório (como find .no shell):

   files = [ 
       os.path.join(parent, name)
       for (parent, subdirs, files) in os.walk(YOUR_DIRECTORY)
       for name in files + subdirs
   ]

Para incluir apenas caminhos completos para arquivos sob o diretório base, deixe de fora + subdirs.


6
import glob
import os

root_dir = <root_dir_here>

for filename in glob.iglob(root_dir + '**/**', recursive=True):
    if os.path.isfile(filename):
        with open(filename,'r') as file:
            print(file.read())

**/**é usado para obter todos os arquivos recursivamente, incluindo directory.

if os.path.isfile(filename)é usado para verificar se a filenamevariável é fileou directory, se é um arquivo, podemos ler esse arquivo. Aqui estou imprimindo arquivo.


6

Eu achei o seguinte o mais fácil

from glob import glob
import os

files = [f for f in glob('rootdir/**', recursive=True) if os.path.isfile(f)]

Usar glob('some/path/**', recursive=True)obtém todos os arquivos, mas também inclui nomes de diretório. A adição da if os.path.isfile(f)condição filtra esta lista apenas aos arquivos existentes


3

use os.path.join()para construir seus caminhos - é mais limpo:

import os
import sys
rootdir = sys.argv[1]
for root, subFolders, files in os.walk(rootdir):
    for folder in subFolders:
        outfileName = os.path.join(root,folder,"py-outfile.txt")
        folderOut = open( outfileName, 'w' )
        print "outfileName is " + outfileName
        for file in files:
            filePath = os.path.join(root,file)
            toWrite = open( filePath).read()
            print "Writing '" + toWrite + "' to" + filePath
            folderOut.write( toWrite )
        folderOut.close()

Parece que esse código funciona apenas para pastas de 2 níveis (ou mais). Ainda assim, me aproxima.
Brock Woolf

1

os.walkfaz caminhada recursiva por padrão. Para cada dir, a partir do raiz, produz três tuplas (caminho de diretório, nomes de diretórios, nomes de arquivos)

from os import walk
from os.path import splitext, join

def select_files(root, files):
    """
    simple logic here to filter out interesting files
    .py files in this example
    """

    selected_files = []

    for file in files:
        #do concatenation here to get full path 
        full_path = join(root, file)
        ext = splitext(file)[1]

        if ext == ".py":
            selected_files.append(full_path)

    return selected_files

def build_recursive_dir_tree(path):
    """
    path    -    where to begin folder scan
    """
    selected_files = []

    for root, dirs, files in walk(path):
        selected_files += select_files(root, files)

    return selected_files

1
No Python 2.6 walk() , retorne lista recursiva. Eu tentei o seu código e tem uma lista com muitas repetições ... Se você simplesmente remover linhas sob o comentário "# chamadas recursivas em subpastas" - ele funciona muito bem
borisbn

1

Tente o seguinte:

import os
import sys

for root, subdirs, files in os.walk(path):

    for file in os.listdir(root):

        filePath = os.path.join(root, file)

        if os.path.isdir(filePath):
            pass

        else:
            f = open (filePath, 'r')
            # Do Stuff

Por que você faria outro listdir () e depois isdir () quando você já tem a lista de diretórios dividida em arquivos e diretórios de walk ()? Parece que seria um pouco lento em árvores grandes (faça três syscalls em vez de um: 1 = walk, 2 = listdir, 3 = isdir, em vez de apenas caminhar e percorrer os 'subdirs' e 'files').
26719 Luc

0

Acho que o problema é que você não está processando a saída os.walkcorretamente.

Primeiro, mude:

filePath = rootdir + '/' + file

para:

filePath = root + '/' + file

rootdiré seu diretório inicial fixo; rooté um diretório retornado por os.walk.

Em segundo lugar, você não precisa recuar seu loop de processamento de arquivos, pois não faz sentido executá-lo para cada subdiretório. Você será rootdefinido para cada subdiretório. Você não precisa processar os subdiretórios manualmente, a menos que queira fazer algo com os próprios diretórios.


Eu tenho dados em cada subdiretório, então preciso ter um arquivo de texto separado para o conteúdo de cada diretório.
Brock Woolf

@Brock: a parte dos arquivos é a lista de arquivos no diretório atual. Portanto, o recuo está realmente errado. Você está escrevendo para filePath = rootdir + '/' + file, isso não parece certo: o arquivo é da lista de arquivos atuais, então você está gravando para muitos arquivos existentes?
Alok Singhal
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.