# How to List Files in a Directory Using Bash: The pi‑web Approach

> Learn to list files in a directory using Bash with the pi-web repository. Explore server-side Node.js and REST API methods for efficient file listing.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: how-to-guide
- Published: 2026-08-11

---

**The pi‑web repository provides two built‑in methods for listing directory contents: a server‑side Node.js utility (`listDirectories`) and a REST API endpoint (`GET /api/files/[…path]`), both of which can be invoked from Bash scripts.**

The [agegr/pi‑web](https://github.com/agegr/pi-web) project implements a secure, consistent file‑browsing system that you can tap into from the command line. Whether you need to list files in a directory using Bash for automation, monitoring, or integration tasks, pi‑web offers both direct library access and an HTTP interface. Both approaches share the same core logic in [`lib/directory-browser.ts`](https://github.com/agegr/pi-web/blob/main/lib/directory-browser.ts), ensuring identical behavior and security policies.

## Method 1: Query the HTTP API from Bash

The simplest way to list files in a directory using Bash is to call the built‑in REST endpoint. This requires no Node.js knowledge and works from any shell environment.

The endpoint `GET /api/files/[…path]?type=list` performs path normalization, symlink resolution, and security filtering before returning a JSON array of directory entries. It automatically excludes common directories like `node_modules` and `.git`.

```bash
#!/usr/bin/env bash

# List directory contents via pi‑web HTTP API

BASE_URL="http://localhost:30141/api/files"
TARGET="${1:-~}"   # First argument or home directory

# URL‑encode the path (strip leading slash)

ENCODED_PATH=$(python3 -c "
import urllib.parse, sys
print(urllib.parse.quote(sys.argv[1].lstrip('/')))
" "$TARGET")

curl -s "${BASE_URL}/${ENCODED_PATH}?type=list" |
  jq -r '.entries[] | "\(.isDir?"📁":"📄") \(.name)"'

```

Save and run: `chmod +x list-files.sh && ./list-files.sh /var/log`

### Security Features of the API Route

The API route at `app/api/files/[...path]/route.ts` implements several protections:

- **Path validation** via `resolveDirectory()` from [`lib/directory-browser.ts`](https://github.com/agegr/pi-web/blob/main/lib/directory-browser.ts)
- **Symlink resolution** to prevent directory‑traversal attacks
- **Root allow‑list** enforcement through [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts)
- **Ignored pattern filtering** (`.git`, `node_modules`, `__pycache__`, etc.)

## Method 2: Invoke the Node.js Library Directly

For server‑side scripts or custom tooling written in TypeScript, call `listDirectories()` directly. This bypasses HTTP overhead and provides typed results.

```typescript
import { resolveDirectory, listDirectories } from '@/lib/directory-browser';

// Resolve and validate the path
const dir = await resolveDirectory('~/projects');

// Get typed array of sub‑directories
const subDirs = await listDirectories(dir);

for (const { name, path, isDirectory } of subDirs) {
  console.log(`${isDirectory ? 'dir' : 'file'}: ${name} at ${path}`);
}

```

The `listDirectories` function in [`lib/directory-browser.ts`](https://github.com/agegr/pi-web/blob/main/lib/directory-browser.ts) returns objects with these properties:

- `name` — entry basename
- `path` — absolute resolved path
- `isDirectory` — boolean from `resolveDirentIsDirectory` helper

## Core Implementation Files

Understanding these source files helps you extend or debug pi‑web's file‑listing behavior:

| File | Purpose |
|------|---------|
| [`lib/directory-browser.ts`](https://github.com/agegr/pi-web/blob/main/lib/directory-browser.ts) | Core utilities: `resolveDirectory()`, `listDirectories()`, `resolveDirentIsDirectory()` |
| `app/api/files/[...path]/route.ts` | HTTP handler for `?type=list` requests; delegates to directory‑browser |
| [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts) | Allowed‑roots configuration and path‑validation guards |
| [`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts) | Client‑side fetcher for UI file explorer (calls same API) |

## Comparison: Which Method to Use

| Approach | Best For | Latency | Dependencies |
|----------|----------|---------|--------------|
| **HTTP API** | Bash scripts, external tools, cron jobs | Higher (network) | `curl`, `jq` |
| **Direct `listDirectories`** | Server‑side TypeScript, custom integrations | Lower (in‑process) | Node.js runtime |

Both methods respect the same ignore patterns and security constraints defined in the source code.

## Summary

- **Primary method for Bash:** Use `curl` against `GET /api/files/[path]?type=list` with URL‑encoded paths
- **Server‑side alternative:** Import `listDirectories` from [`lib/directory-browser.ts`](https://github.com/agegr/pi-web/blob/main/lib/directory-browser.ts) for typed, synchronous‑style enumeration
- **Security consistency:** Both paths invoke `resolveDirectory()` and `resolveDirentIsDirectory()` from the same core module
- **Data filtering:** Automatic exclusion of `node_modules`, `.git`, and configurable junk patterns

## Frequently Asked Questions

### How do I handle paths with spaces when calling the pi‑web API from Bash?

Quote the argument when invoking your script: `./list-files.sh "/path/with spaces"`. The Python `urllib.parse.quote()` in the example properly encodes spaces as `%20`, ensuring the URL remains valid. The [`route.ts`](https://github.com/agegr/pi-web/blob/main/route.ts) handler decodes these automatically before validation.

### Can I list files recursively instead of just one directory?

The built‑in `?type=list` parameter returns a single level. For recursion, either iterate in your Bash script (making successive API calls for each subdirectory) or extend [`lib/directory-browser.ts`](https://github.com/agegr/pi-web/blob/main/lib/directory-browser.ts) to expose a recursive variant. The `resolveDirentIsDirectory` helper already provides the `isDirectory` flag needed for traversal logic.

### What ports and authentication does the HTTP API require?

By default, pi‑web listens on port `30141` as shown in the examples. Authentication depends on your deployment; the [`useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/useAgentSession.ts) hook suggests session‑based auth for browser clients. For programmatic Bash access, check your [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts) configuration—it may allow localhost requests without credentials or require a bearer token in the `Authorization` header.