Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
51: Building a Command-Line Tool in Swift
Up until now, we've mostly been writing code that lives inside an app or a playground. But one of the most satisfying things you can do with Swift is build a tool that lives in your terminal. I use these all the time for automating boring parts of my workflow—things like renaming batches of files or parsing JSON logs.
For this lesson, let's build a tool called wordly. The goal is simple: it should take a path to a text file as an argument, count the words, and tell us which word appears most frequently. It's a classic utility, but it covers all the bases of CLI development.
Setting up the Executable Package
You can't just run a random .swift file and expect it to behave like a professional tool. We need a proper package structure. Open your terminal and run:
mkdir wordly
cd wordly
swift package init --type executable
I use --type executable because it tells Swift Package Manager (SPM) to create a main.swift file (or a @main entry point). This is the "front door" of your program. Without this, the compiler wouldn't know where the execution starts.
Dealing with Command-Line Arguments
Now, let's look at how we get data into the tool. Swift provides the CommandLine.arguments array. The first element is always the path to the executable itself, so our actual input starts at index 1.
Here is my first pass at the logic. I'm just going to grab the filename and try to read it:
import Foundation
let fileName = CommandLine.arguments[1]
let content = try String(contentsOfFile: fileName)
print("Reading file: \(fileName)")
I ran this immediately, and it crashed. Fatal error: Index out of range. I did exactly what I always do when I'm rushing: I assumed the user would actually provide a filename. If you just run swift run without arguments, the program dies instantly.
We need to be defensive. I'll swap that out for a guard statement to provide a helpful usage message instead of a crash:
import Foundation
guard CommandLine.arguments.count > 1 else {
print("Usage: wordly [file-path]")
exit(1) // Exit with an error code
}
let fileName = CommandLine.arguments[1]
Processing the Text
Now that we have the file path, we need to actually analyze the text. I want to ignore punctuation and case sensitivity, otherwise "Apple" and "apple," would be treated as different words. I'll use a Dictionary to keep track of the counts.
do {
let content = try String(contentsOfFile: fileName, encoding: .utf8)
// Split by whitespace and punctuation
let words = content.lowercased()
.components(separatedBy: CharacterSet.alphanumerics.inverted)
.filter { !$0.isEmpty }
var counts: [String: Int] = [:]
for word in words {
counts[word, default: 0] += 1
}
if let mostFrequent = counts.max(by: { $0.value < $1.value }) {
print("Total words: \(words.count)")
print("Most frequent word: '\(mostFrequent.key)' (\(mostFrequent.value) occurrences)")
} else {
print("The file is empty.")
}
} catch {
print("Error: Could not read file at \(fileName)")
exit(1)
}
I used CharacterSet.alphanumerics.inverted here. It's a handy trick—basically, I'm telling Swift, "Split this string whenever you hit anything that isn't a letter or a number." It's much cleaner than trying to list every possible punctuation mark manually.
Running and Distributing the Tool
To test it, create a quick text file in your folder: echo "Swift is great. Swift is fast." > test.txt. Then run your tool:
swift run wordly test.txt
If you want to use this tool anywhere on your system without typing swift run, you can build it in release mode:
swift build -c release
You'll find the binary in the .build/release/ folder. You can move that binary to /usr/local/bin, and suddenly wordly becomes a first-class citizen in your terminal.
📋 Practical Task
Build the "Line-Finder" Utility
Your task is to build a new command-line tool called linefinder. This tool should take two arguments: a file path and a search term. The tool should read the file and print every line number where the search term appears.
Requirements:
- Ensure the program prints a "Usage" message and exits gracefully if either the file path or search term is missing.
- The search should be case-insensitive.
- The output should follow this format:
Line 4: [The content of the line]. - Handle the error if the file cannot be found or read.
There are no comments for now.