C# Pastas e Caminhos
Nas aulas Arquivos e Ler e Escrever, todos os arquivos ficavam na mesma pasta do programa. Programas de verdade guardam relatórios em subpastas, procuram fotos na pasta Downloads e organizam arquivos. Nesta aula você vai usar a classe Path ("caminho") para montar e desmontar endereços de arquivos, e a classe Directory ("diretório", outro nome para pasta) para criar, listar, mover e apagar pastas. No fim, vai escrever um programa que arruma a pasta de downloads da Ana.
- Montar caminhos com
Path.Combinee desmontá-los comGetFileName,GetExtensioneGetDirectoryName. - A diferença entre caminho relativo e absoluto, e o que é a pasta atual.
- Encontrar a pasta pessoal com
Environment.GetFolderPath. - Criar pastas com
Directory.CreateDirectorye listar comGetFileseGetDirectories, com filtro e subpastas. - Ver tamanho, nome e extensão com
FileInfoeDirectoryInfo. - Mover e apagar pastas, e por que
Directory.Deletereclama de pasta cheia.
Os programas desta aula rodam com dotnet run na pasta ~/cs da Ana. A pasta pessoal dela, /home/ana, tem as pastas de sempre do Ubuntu em português: Documentos, Downloads, Imagens, Música…
$ ls ~/Downloads ~/Documentos /home/ana/Documentos: acesso.log alunos.csv notas.txt projetos relatorio.odt /home/ana/Downloads: foto.jpg fotos-viagem.zip manual.pdf musica.mp3 ubuntu-26.04-desktop-amd64.iso
A ideia: o endereço e o prédio
Um caminho é um endereço: "Rua Documentos, prédio relatorios, apartamento outubro.txt". Escrever um endereço num papel não constrói prédio nenhum. A classe Path trabalha só com o papel: junta, separa e confere pedaços de texto, sem tocar no disco. A classe Directory trabalha com os prédios: ela cria, lista e apaga pastas de verdade.
| Classe | Mexe no disco? | Exemplos |
|---|---|---|
Path | Não: só texto | Path.Combine, Path.GetExtension |
Directory | Sim: pastas | Directory.CreateDirectory, Directory.GetFiles |
File | Sim: arquivos | File.ReadAllText, File.Move (aula Arquivos) |
No Linux e no macOS, as partes de um caminho são separadas por /. No Windows, por \. Se você monta os caminhos com Path, o mesmo programa funciona nos três.
1. Montar e desmontar caminhos: Path
string caminho = Path.Combine("Documentos", "relatorios", "outubro.txt");
Console.WriteLine(caminho);
Console.WriteLine(Path.GetFileName(caminho));
Console.WriteLine(Path.GetFileNameWithoutExtension(caminho));
Console.WriteLine(Path.GetExtension(caminho));
Console.WriteLine(Path.GetDirectoryName(caminho));
Console.WriteLine(Path.ChangeExtension(caminho, ".csv"));
Console.WriteLine(Path.GetFullPath(caminho));
Console.WriteLine(Path.IsPathRooted(caminho));
Console.WriteLine(Path.DirectorySeparatorChar);
$ dotnet run path.cs Documentos/relatorios/outubro.txt outubro.txt outubro .txt Documentos/relatorios Documentos/relatorios/outubro.csv /home/ana/cs/Documentos/relatorios/outubro.txt False /
- Path.Combine("Documentos", "relatorios", "outubro.txt")
- "Combinar": junta as partes com o separador do sistema. No Linux,
Documentos/relatorios/outubro.txt; no Windows,Documentos\relatorios\outubro.txt. Aceita quantas partes você quiser. - Path.GetFileName
- O último pedaço, o nome do arquivo:
outubro.txt. - Path.GetFileNameWithoutExtension
- O nome sem a extensão:
outubro. - Path.GetExtension
- A extensão, com o ponto:
.txt. Sem extensão, devolve"". - Path.GetDirectoryName
- Tudo menos o último pedaço: a pasta onde o arquivo está.
- Path.ChangeExtension(caminho, ".csv")
- O mesmo caminho com outra extensão. Só muda o texto: o arquivo no disco continua com o nome antigo (para renomear de verdade,
File.Move). - Path.GetFullPath
- O caminho absoluto, a partir da raiz
/, completando com a pasta atual (seção 2). - Path.IsPathRooted
- "Começa na raiz?"
False: este caminho é relativo. - Path.DirectorySeparatorChar
- O separador do sistema, como
char:/no Linux.
Nenhuma dessas linhas conferiu se Documentos/relatorios/outubro.txt existe. E ele não existe: a classe Path só trabalha com o texto.
Missão: a Ana converteu a foto da praia para PNG. Acrescente uma linha que mostre o caminho da foto com a extensão .png, usando Path.ChangeExtension. A última linha da saída deve ser Documentos/fotos/praia.png.
string foto = Path.Combine("Documentos", "fotos", "praia.jpg");
Console.WriteLine(foto);
Console.WriteLine(Path.GetFileName(foto));
Console.WriteLine(Path.GetDirectoryName(foto));
string foto = Path.Combine("Documentos", "fotos", "praia.jpg");
Console.WriteLine(foto);
Console.WriteLine(Path.GetFileName(foto));
Console.WriteLine(Path.GetDirectoryName(foto));
Console.WriteLine(Path.ChangeExtension(foto, ".png"));
2. Relativo × absoluto, e a pasta atual
Um caminho absoluto começa na raiz, /, e aponta sempre para o mesmo lugar: /home/ana/Documentos/notas.txt. Um caminho relativo, como notas.txt ou ../Documentos/notas.txt, é completado com a pasta atual: a pasta em que o terminal estava quando o programa começou (o pwd do terminal). O .. quer dizer "a pasta de cima".
Console.WriteLine(Path.GetFullPath("notas.txt"));
Console.WriteLine(Path.GetFullPath("../Documentos/notas.txt"));
$ dotnet run abs.cs /home/ana/cs/notas.txt /home/ana/Documentos/notas.txt $ cd .. « agora a pasta atual é /home/ana $ dotnet run cs/abs.cs /home/ana/notas.txt /home/Documentos/notas.txt
O mesmo programa, rodado de outra pasta, procura os arquivos em outro lugar. É a causa número um de FileNotFoundException "misteriosa": o arquivo está lá, mas o programa foi iniciado em outra pasta.
A pasta pessoal e outras pastas do sistema
Para um caminho que funcione de qualquer pasta, comece pela pasta pessoal do usuário. Quem diz qual é ela é o Environment ("ambiente"):
string casa = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
Console.WriteLine($"Pasta pessoal: {casa}");
Console.WriteLine($"Pasta atual: {Directory.GetCurrentDirectory()}");
Console.WriteLine($"Temporária: {Path.GetTempPath()}");
string notas = Path.Combine(casa, "Documentos", "notas.txt");
Console.WriteLine($"{notas} existe? {File.Exists(notas)}");
$ dotnet run pastas.cs Pasta pessoal: /home/ana Pasta atual: /home/ana/cs Temporária: /tmp/ /home/ana/Documentos/notas.txt existe? True
- Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
- "A pasta do perfil do usuário":
/home/anano Linux,/Users/anano macOS eC:\Users\anano Windows.SpecialFolderé um enum com dezenas de pastas especiais. - Directory.GetCurrentDirectory()
- A pasta atual. (
Environment.CurrentDirectoryé o mesmo.) - Path.GetTempPath()
- A pasta de arquivos temporários, que o sistema pode limpar:
/tmp/no Linux.
Os nomes das pastas pessoais dependem do idioma do sistema: no Ubuntu em português é Documentos, em inglês é Documents. O SpecialFolder.MyDocuments tenta descobrir pela configuração do sistema, e o resultado muda de máquina para máquina: na máquina da Ana, /home/ana/Documentos; no editor do guia, um texto vazio. O UserProfile é o único que existe em todo lugar. Para dados do seu programa, uma escolha comum é uma pasta própria dentro dele, como Path.Combine(casa, ".lojinha").
3. Criar pastas e listar o conteúdo: Directory
string pasta = Path.Combine("relatorios", "2026", "outubro");
DirectoryInfo d = Directory.CreateDirectory(pasta);
Console.WriteLine($"Criei {d.FullName}");
File.WriteAllText(Path.Combine(pasta, "vendas.txt"), "Vendas: 1520\n");
File.WriteAllText(Path.Combine(pasta, "gastos.txt"), "Gastos: 830\n");
Directory.CreateDirectory(pasta);
foreach (string f in Directory.GetFiles("relatorios", "*.txt", SearchOption.AllDirectories).Order())
{
Console.WriteLine(f);
}
foreach (string p in Directory.GetDirectories("relatorios"))
{
Console.WriteLine($"pasta: {p}");
}
$ dotnet run criar.cs Criei /home/ana/cs/relatorios/2026/outubro relatorios/2026/outubro/gastos.txt relatorios/2026/outubro/vendas.txt pasta: relatorios/2026
- Directory.CreateDirectory(pasta)
- Cria a pasta e todas as que faltarem no caminho (
relatorios,2026eoutubro), como omkdir -pdo terminal. Se ela já existe, não faz nada e não reclama: por isso a segunda chamada, mais abaixo, passou sem erro. Devolve umDirectoryInfo(seção 5), aqui usado para mostrar o caminho completo. - Directory.GetFiles("relatorios", "*.txt", SearchOption.AllDirectories)
- Os caminhos dos arquivos da pasta, num
string[]. O segundo argumento é um filtro:*quer dizer "qualquer coisa", então*.txté "tudo que termina em .txt". O terceiro diz para procurar também dentro das subpastas (o padrão éSearchOption.TopDirectoryOnly, só a própria pasta). - .Order()
- Põe os caminhos em ordem alfabética (aula Ordenar). A seção 4 explica por quê.
- Directory.GetDirectories("relatorios")
- As subpastas, só do primeiro nível:
relatorios/2026, e nãooutubro, que está dentro dela.
Os caminhos que voltam são do mesmo tipo que você passou: com "relatorios" (relativo), eles voltam relativos. Para só o nome, use Path.GetFileName em cada um.
4. A ordem da lista não é garantida
O GetFiles devolve os arquivos na ordem em que o sistema de arquivos os guarda, que não é a alfabética e pode mudar de um computador para outro:
string casa = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
foreach (string a in Directory.GetFiles(Path.Combine(casa, "Downloads")))
{
Console.WriteLine(Path.GetFileName(a));
}
$ dotnet run ordem.cs fotos-viagem.zip manual.pdf ubuntu-26.04-desktop-amd64.iso musica.mp3 foto.jpg
O ls do terminal ordena antes de mostrar; o GetFiles, não. Quando a ordem importa (para mostrar ao usuário, para comparar duas listas), ordene: Directory.GetFiles(…).Order(), ou Array.Sort(lista) (aula A Classe Array).
5. Detalhes do arquivo: FileInfo e DirectoryInfo
As classes File e Directory respondem uma pergunta por vez, com o caminho em cada chamada. As classes FileInfo ("informação do arquivo") e DirectoryInfo são objetos que representam um arquivo ou uma pasta, com propriedades prontas: nome, extensão, tamanho, data.
string casa = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
DirectoryInfo docs = new DirectoryInfo(Path.Combine(casa, "Documentos"));
Console.WriteLine($"{docs.Name} existe? {docs.Exists}");
foreach (FileInfo f in docs.GetFiles().OrderBy(f => f.Name))
{
Console.WriteLine($"{f.Name,-15} {f.Extension,-5} {f.Length,6} bytes");
}
foreach (DirectoryInfo sub in docs.GetDirectories())
{
Console.WriteLine($"[{sub.Name}]");
}
long total = 0;
foreach (string a in Directory.EnumerateFiles(docs.FullName, "*", SearchOption.AllDirectories))
{
total += new FileInfo(a).Length;
}
Console.WriteLine($"Total: {total} bytes");
$ dotnet run docs.cs Documentos existe? True acesso.log .log 389 bytes alunos.csv .csv 132 bytes notas.txt .txt 69 bytes relatorio.odt .odt 8394 bytes [projetos] Total: 9065 bytes
- new DirectoryInfo(caminho)
- Um objeto que representa a pasta. Criá-lo não cria a pasta:
docs.Existsdiz se ela existe (edocs.Create()cria). - docs.GetFiles()
- Devolve um
FileInfo[], e não umstring[]: cada item já sabe o seuName,Extension,Length(tamanho em bytes),FullName(caminho completo) eLastWriteTime(data da última mudança). - .OrderBy(f => f.Name)
- Ordena pelo nome (aulas Lambdas e Ordenar).
- {f.Name,-15}
- Alinha o texto em 15 colunas, à esquerda;
{f.Length,6}alinha o número em 6, à direita (aula Formatar Números). - Directory.EnumerateFiles(…)
- Igual ao
GetFiles, mas entrega um caminho por vez, como oFile.ReadLinesda aula Arquivos. Para pastas com milhares de arquivos, começa a responder antes e gasta menos memória.
O total inclui os arquivos de dentro de projetos, por causa do SearchOption.AllDirectories: 389 + 132 + 69 + 8394 são 8984, e os 81 bytes restantes estão na subpasta.
6. Mover e apagar pastas
Directory.CreateDirectory(Path.Combine("rascunhos", "contos"));
File.WriteAllText(Path.Combine("rascunhos", "ideias.txt"), "um robô jardineiro\n");
Directory.Move("rascunhos", "textos");
Console.WriteLine(Directory.Exists("rascunhos"));
Console.WriteLine(File.Exists(Path.Combine("textos", "ideias.txt")));
Directory.Delete(Path.Combine("textos", "contos"));
Console.WriteLine(Directory.Exists(Path.Combine("textos", "contos")));
Directory.Delete("textos", recursive: true);
Console.WriteLine(Directory.Exists("textos"));
$ dotnet run mover.cs False True False False
- Directory.Move("rascunhos", "textos")
- Move ou renomeia a pasta, com tudo o que tem dentro. Depois dele,
rascunhosnão existe mais, e oideias.txtestá emtextos. - Directory.Delete(caminho)
- Apaga uma pasta vazia. A
contosestava vazia, então foi. - Directory.Delete("textos", recursive: true)
- "Recursivo": apaga a pasta e tudo o que estiver dentro, subpastas incluídas, sem lixeira. É o
rm -rdo terminal. Confira duas vezes o caminho antes de rodar.
Formas de fazer: qual usar
| Você quer… | Use |
|---|---|
| Juntar pedaços de um caminho | Path.Combine(a, b, c), nunca a + "/" + b |
| Só o nome, a extensão ou a pasta | Path.GetFileName, GetExtension, GetDirectoryName |
| Um caminho que funcione de qualquer pasta | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), …) |
| Garantir que uma pasta existe | Directory.CreateDirectory (não reclama se já existir) |
| Os caminhos dos arquivos de uma pasta | Directory.GetFiles(pasta, filtro), ordenado com .Order() |
| Muitos arquivos, um por vez | Directory.EnumerateFiles |
| Nome, tamanho e data de cada arquivo | new DirectoryInfo(pasta).GetFiles() ou new FileInfo(caminho) |
| Apagar uma pasta com conteúdo | Directory.Delete(pasta, recursive: true) |
Quando dá errado
IOException: Directory not empty
try
{
Directory.Delete("relatorios");
}
catch (IOException e)
{
Console.WriteLine($"{e.GetType().Name}: {e.Message}");
}
$ dotnet run apagar.cs IOException: Directory not empty : '/home/ana/cs/relatorios'
O que aconteceu: "A pasta não está vazia". Sem o segundo argumento, o Directory.Delete só apaga pastas vazias, para você não perder arquivos sem querer. A relatorios da seção 3 tem a 2026 e dois arquivos dentro.
Como resolver: se é isso mesmo que você quer, Directory.Delete("relatorios", recursive: true). Se não, apague antes os arquivos que você conhece, um por um, com File.Delete.
DirectoryNotFoundException ao listar
string[] fotos = Directory.GetFiles("Fotos");
$ dotnet run fotos.cs Unhandled exception. System.IO.DirectoryNotFoundException: Could not find a part of the path '/home/ana/cs/Fotos'. at System.IO.Enumeration.FileSystemEnumerator`1.CreateDirectoryHandle(String path, Boolean ignoreNotFound) at System.IO.Enumeration.FileSystemEnumerator`1.Init() … « mais 4 linhas de dentro do .NET at Program.<Main>$(String[] args) in /home/ana/cs/fotos.cs:line 1
O que aconteceu: a pasta Fotos não existe dentro de ~/cs. (As fotos da Ana estão em ~/Imagens.) O caminho completo da mensagem mostra onde o .NET procurou.
Como resolver: confira com Directory.Exists antes de listar, ou monte o caminho a partir da pasta pessoal: Path.Combine(casa, "Imagens").
Path.Combine jogou a pasta fora
Console.WriteLine(Path.Combine("/home/ana", "/Documentos", "notas.txt"));
Console.WriteLine(Path.Join("/home/ana", "/Documentos", "notas.txt"));
$ dotnet run combine.cs /Documentos/notas.txt /home/ana/Documentos/notas.txt
O que aconteceu: a segunda parte, "/Documentos", começa com /, então é um caminho absoluto. Quando uma das partes é absoluta, o Path.Combine descarta tudo o que veio antes e começa dali. Nenhum erro: o programa simplesmente procura no lugar errado.
Como resolver: escreva as partes sem a barra no começo: Path.Combine("/home/ana", "Documentos", "notas.txt"). Se as partes vêm de fora (digitadas pelo usuário, lidas de um arquivo), use Path.Join, que só junta e nunca descarta.
+
Console.WriteLine("Documentos" + "notas.txt");
File.WriteAllText("Documentos\\notas.txt", "oi");
$ dotnet run somar.cs Documentosnotas.txt $ ls Documentos\notas.txt somar.cs
O que aconteceu: na primeira linha, faltou a barra entre a pasta e o arquivo. Na segunda, a Ana copiou um caminho do Windows, com \ (escrita \\ dentro da string, aula Escapes). No Linux, \ não separa pastas: é uma letra comum. O programa criou na pasta cs um arquivo chamado, literalmente, Documentos\notas.txt.
Como resolver: deixe o Path.Combine("Documentos", "notas.txt") escolher o separador. Para apagar o arquivo estranho no terminal, use aspas: rm 'Documentos\notas.txt'.
Referência rápida
| Código | O que faz |
|---|---|
Path.Combine(a, b, …) / Path.Join(a, b, …) | Junta as partes com o separador (o Join nunca descarta partes) |
Path.GetFileName(c) / GetFileNameWithoutExtension(c) | Nome, com e sem extensão |
Path.GetExtension(c) | Extensão com o ponto (.txt) |
Path.GetDirectoryName(c) | A pasta do caminho |
Path.ChangeExtension(c, ".csv") | O texto do caminho com outra extensão |
Path.GetFullPath(c) | Caminho absoluto |
Path.GetTempPath() | Pasta temporária |
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) | Pasta pessoal (/home/ana) |
Directory.GetCurrentDirectory() | Pasta atual |
Directory.Exists(c) | A pasta existe? |
Directory.CreateDirectory(c) | Cria a pasta e as que faltarem |
Directory.GetFiles(c, "*.txt", SearchOption.AllDirectories) | Arquivos (filtro e subpastas opcionais), sem ordem garantida |
Directory.GetDirectories(c) | Subpastas do primeiro nível |
Directory.EnumerateFiles(c) | Arquivos, um por vez |
Directory.Move(a, b) | Move ou renomeia a pasta |
Directory.Delete(c) / Directory.Delete(c, recursive: true) | Apaga a pasta vazia / com tudo dentro |
new FileInfo(c): Name, Extension, Length, LastWriteTime | Detalhes de um arquivo |
new DirectoryInfo(c): GetFiles(), GetDirectories() | Detalhes e conteúdo de uma pasta |
Exercícios
1. Juntar as partes
Complete para montar o caminho Imagens/ferias.jpg com o separador certo do sistema:
2. A extensão
O que Path.GetExtension("musica.mp3") devolve?
3. Apagar com tudo
Complete para apagar a pasta antigos mesmo que ela tenha arquivos dentro:
4. O que Directory.CreateDirectory("a/b/c") faz se a pasta a não existe?
5. O programa lê "dados.txt" e funciona quando rodado em ~/cs. Rodado em ~, dá FileNotFoundException. Por quê?
6. Quanto vale Path.Combine("/home/ana", "/tmp", "x.txt")?
Missão final: arrumar os downloads
Missão: o programa cria uma pasta Downloads com cinco arquivos misturados. Complete o laço para guardar cada arquivo numa subpasta com o nome da extensão (Downloads/jpg/foto.jpg, Downloads/pdf/manual.pdf…):
- descubra a extensão com
Path.GetExtension(arquivo)e tire o ponto com.TrimStart('.'); - monte o caminho da subpasta com
Path.Combine(origem, extensao)e crie-a comDirectory.CreateDirectory; - mova o arquivo para lá com
File.Move(arquivo, Path.Combine(destino, Path.GetFileName(arquivo)), overwrite: true).
A saída deve ser:
Arquivos soltos: 0 jpg: 2 mp3: 1 pdf: 2
string origem = "Downloads";
Directory.CreateDirectory(origem);
string[] nomes = ["foto.jpg", "manual.pdf", "praia.jpg", "musica.mp3", "boleto.pdf"];
foreach (string nome in nomes)
{
File.WriteAllText(Path.Combine(origem, nome), "");
}
foreach (string arquivo in Directory.GetFiles(origem))
{
// 1. a extensão sem o ponto
// 2. a subpasta com o nome da extensão
// 3. mova o arquivo para a subpasta
}
Console.WriteLine($"Arquivos soltos: {Directory.GetFiles(origem).Length}");
foreach (string pasta in Directory.GetDirectories(origem).Order())
{
Console.WriteLine($"{Path.GetFileName(pasta)}: {Directory.GetFiles(pasta).Length}");
}
string origem = "Downloads";
Directory.CreateDirectory(origem);
string[] nomes = ["foto.jpg", "manual.pdf", "praia.jpg", "musica.mp3", "boleto.pdf"];
foreach (string nome in nomes)
{
File.WriteAllText(Path.Combine(origem, nome), "");
}
foreach (string arquivo in Directory.GetFiles(origem))
{
string extensao = Path.GetExtension(arquivo).TrimStart('.');
string destino = Path.Combine(origem, extensao);
Directory.CreateDirectory(destino);
File.Move(arquivo, Path.Combine(destino, Path.GetFileName(arquivo)), overwrite: true);
}
Console.WriteLine($"Arquivos soltos: {Directory.GetFiles(origem).Length}");
foreach (string pasta in Directory.GetDirectories(origem).Order())
{
Console.WriteLine($"{Path.GetFileName(pasta)}: {Directory.GetFiles(pasta).Length}");
}
Os arquivos são criados vazios ("") só para o exemplo. O GetFiles(origem) do laço é chamado uma vez, antes de o laço começar, então mover arquivos lá dentro não atrapalha a lista. Rodando de novo, o resultado é o mesmo: os arquivos são recriados soltos e o overwrite: true deixa o Move substituir os que já estavam nas subpastas. Se quiser ir além: em vez da extensão, separe por tipo (Imagens para .jpg e .png, Documentos para .pdf) com uma expressão switch.
Resumo
Pathsó mexe no texto do caminho:Combinejunta,GetFileName,GetExtensioneGetDirectoryNameseparam. Monte caminhos com ele, nunca com+.- Caminhos relativos são completados com a pasta atual, onde o programa foi iniciado. Para um caminho fixo, comece da pasta pessoal:
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile). Directory.CreateDirectorycria a pasta e as que faltarem, sem reclamar se já existe.Directory.GetFileseGetDirectorieslistam (com filtro*.txteSearchOption.AllDirectories); a ordem não é garantida, então ordene.FileInfoeDirectoryInfodão nome, extensão, tamanho e data como propriedades.Directory.Deletesó apaga pasta vazia; comrecursive: true, apaga tudo, sem lixeira. Uma parte absoluta noPath.Combinedescarta as anteriores.