Build a command-line tool that scans a directory, finds all empty files, and optionally writes them to an output file.
What You’ll Build
A utility program that:
Accepts a directory path as a command-line argument
Scans all files in that directory
Identifies files with zero size
Prints the names of empty files
Optionally writes results to a file
Optimizes memory usage with proper slice allocation
What You’ll Learn
Reading directory contents with ioutil.ReadDir
Working with file metadata (size, name, etc.)
Writing data to files
Slice capacity optimization
Error handling for file operations
Byte slice manipulation
Step-by-Step Approach
Fetch the Files
Read the directory and find empty files. package main
import (
" fmt "
" io/ioutil "
" os "
)
func main () {
args := os . Args [ 1 :]
if len ( args ) == 0 {
fmt . Println ( "Provide a directory" )
return
}
// Read all files in the directory
files , err := ioutil . ReadDir ( args [ 0 ])
if err != nil {
fmt . Println ( err )
return
}
// Find and print empty files
for _ , file := range files {
if file . Size () == 0 {
name := file . Name ()
fmt . Println ( name )
}
}
}
Tasks:
Check for command-line arguments
Read directory contents
Identify empty files (size == 0)
Print their names
Write to a File
Store the empty file names in an output file. // Create a byte slice to store names
var names [] byte
for _ , file := range files {
if file . Size () == 0 {
name := file . Name ()
// Append filename to slice
names = append ( names , name ... )
names = append ( names , ' \n ' ) // Add newline
}
}
// Write to file
err = ioutil . WriteFile ( "out.txt" , names , 0644 )
if err != nil {
fmt . Println ( err )
return
}
// Also print to console
fmt . Printf ( " %s " , names )
Tasks:
Create a byte slice for output
Append filenames with newlines
Write to a file with proper permissions
Print the results
Optimize Memory Usage
Pre-allocate the slice to avoid multiple reallocations. // Calculate total size needed
var total int
for _ , file := range files {
if file . Size () == 0 {
// +1 for the newline character
total += len ( file . Name ()) + 1
}
}
fmt . Printf ( "Total required space: %d bytes. \n " , total )
// Allocate slice with exact capacity
names := make ([] byte , 0 , total )
// Now append without reallocations
for _ , file := range files {
if file . Size () == 0 {
name := file . Name ()
names = append ( names , name ... )
names = append ( names , ' \n ' )
}
}
Why optimize?
Reduces memory allocations
Improves performance
Avoids slice capacity doubling overhead
Complete Example
package main
import (
" fmt "
" io/ioutil "
" os "
)
func main () {
args := os . Args [ 1 :]
if len ( args ) == 0 {
fmt . Println ( "Provide a directory" )
return
}
files , err := ioutil . ReadDir ( args [ 0 ])
if err != nil {
fmt . Println ( err )
return
}
// Calculate total size for optimization
var total int
for _ , file := range files {
if file . Size () == 0 {
// +1 for newline
total += len ( file . Name ()) + 1
}
}
fmt . Printf ( "Total required space: %d bytes. \n " , total )
// Pre-allocate with exact capacity
names := make ([] byte , 0 , total )
for _ , file := range files {
if file . Size () == 0 {
name := file . Name ()
names = append ( names , name ... )
names = append ( names , ' \n ' )
}
}
err = ioutil . WriteFile ( "out.txt" , names , 0644 )
if err != nil {
fmt . Println ( err )
return
}
fmt . Printf ( " %s " , names )
}
Running the Program
# Create some test files
mkdir test_dir
touch test_dir/empty1.txt
touch test_dir/empty2.txt
echo "content" > test_dir/nonempty.txt
# Run the program
go run main.go test_dir
Output:
Total required space: 22 bytes.
empty1.txt
empty2.txt
Key Concepts
import " io/ioutil "
// Returns a slice of FileInfo
files , err := ioutil . ReadDir ( "/path/to/dir" )
if err != nil {
// Handle error
}
// Each file has methods:
for _ , file := range files {
name := file . Name () // File name
size := file . Size () // Size in bytes
isDir := file . IsDir () // Is it a directory?
mode := file . Mode () // File permissions
}
data := [] byte ( "Hello, World! \n " )
// WriteFile creates or truncates the file
err := ioutil . WriteFile ( "output.txt" , data , 0644 )
// File permissions (Unix):
// 0644 = rw-r--r--
// Owner: read+write
// Group: read
// Others: read
See Unix file permissions for more info.
Slice Capacity Optimization
// Without pre-allocation (inefficient)
var slice [] byte
for i := 0 ; i < 1000 ; i ++ {
slice = append ( slice , ' x ' ) // May reallocate many times
}
// With pre-allocation (efficient)
slice := make ([] byte , 0 , 1000 )
for i := 0 ; i < 1000 ; i ++ {
slice = append ( slice , ' x ' ) // No reallocations needed
}
Check capacity with cap(slice).
String to Byte Conversion
str := "hello"
// Convert string to []byte
bytes := [] byte ( str )
// Append string to []byte slice
var slice [] byte
slice = append ( slice , str ... ) // ... unpacks the string
// Convert []byte to string
str2 := string ( bytes )
File Permission Reference
// Common Unix file permissions:
0644 // -rw-r--r-- (typical file)
0755 // -rwxr-xr-x (executable)
0600 // -rw------- (private file)
0666 // -rw-rw-rw- (world writable)
Use a permissions calculator to understand them better.
Error Handling
Directory Doesn't Exist
Permission Denied
Missing Arguments
files , err := ioutil . ReadDir ( "/nonexistent" )
if err != nil {
fmt . Println ( err )
// Output: open /nonexistent: no such file or directory
return
}
Enhancements to Try
Recursive search - Search subdirectories too
File filters - Find files by extension or pattern
Size threshold - Find files smaller than N bytes
Delete option - Ask to delete empty files
Statistics - Show total files, empty files, space saved
JSON output - Export results in JSON format
Colored output - Highlight empty files in red
Practice Exercises
Try extending the program:
Count Files - Display total count of empty vs non-empty files
Filter by Extension - Only check specific file types
Largest Files - Also report the largest files found
Timestamps - Include file modification times
Interactive Mode - Ask before writing to output file
Security Note: Be careful when automatically deleting files. Always confirm with the user first!
Next Steps
Spam Masker Work with byte manipulation
File I/O Guide Learn more about file operations