List modified files in a git repo
Print the paths of files with uncommitted changes (staged or unstaged) — for pre-commit hooks, CI lint-only-changed gates, and "what am I about to commit".
How to list modified files in a git repo in each shell
git status --porcelain | awk '{print $2}'`--porcelain` is the stable machine-parseable format (`--porcelain=v1` since git 2.0; `=v2` is the richer variant). Column 1 is the 2-char status (`XY`); column 2 is the path. Renames show `R old -> new` — awk `$2` returns `old` (the arrow form breaks naive parsing — use `--porcelain=v2` for unambiguous output).
git status --porcelain | awk '{print $2}'git status --porcelain | awk '{print $2}'git status --porcelain | ForEach-Object { ($_ -split "\s+", 3)[2] }`-split "\s+", 3` splits on whitespace into AT MOST 3 fields — preserves paths containing spaces in the third field. Without the limit, "src/my file.txt" splits into 3+ pieces.
git status --porcelaincmd has no per-line column parser. Output the raw porcelain and let the consumer handle parsing — or shell out to pwsh.
Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.
Gotchas & notes
- `git status --porcelain` (every modified file: staged + unstaged + untracked) vs `git diff --name-only` (unstaged only) vs `git diff --cached --name-only` (staged only) — picking the wrong one yields a silently incomplete list. For "everything that's NOT in HEAD yet": `git diff HEAD --name-only` (combines staged + unstaged) PLUS `git ls-files --others --exclude-standard` (untracked). Or use `git status --porcelain` once and parse the two-char status column: `XY` where X = index status, Y = worktree status; `??` = untracked, `!!` = ignored (only with `--ignored`).
- Pathname safety: file paths can contain spaces, newlines, tabs, even quotes. Naive `awk '{print $2}'` breaks on "my file.txt" (awk's default FS is whitespace → splits on the space). The robust portable form: `git status --porcelain -z | tr '\0' '\n'` (the `-z` flag uses NUL separators between records AND between rename-old/new pairs, which `tr` then renders one-per-line). For shell scripts that need to iterate: `while IFS= read -r -d "" entry; do ...; done < <(git status --porcelain -z)`. NEVER pipe paths through shell expansion (`for f in $(git ...)`) — word-splitting eats the whitespace.
- **Renames** in `--porcelain` v1: `R old -> new` (space-arrow-space separator). In `--porcelain=v2` (git 2.11+): one line per change, fields are `2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path>\t<origPath>` — strictly tab-separated, no in-string arrow. The v2 format is the right choice for any new tooling. Concretely: `git status --porcelain=v2 -z | awk -v RS='\0' '$1==2 { print substr($0, index($0, FS)+1) }'` handles renames cleanly.
- Filtering to just one status: `git diff --name-only --diff-filter=M` (only Modified, no Added/Deleted/Renamed); `--diff-filter=AM` (Added or Modified); uppercase = include, lowercase = exclude (`--diff-filter=d` = "all except deleted"). Useful for lint-on-touched-files CI gates: `git diff --name-only origin/main --diff-filter=AM -- "*.ts" | xargs eslint`. pwsh: pipe through `Where-Object { $_ -match "\.ts$" }` rather than glob inside git.
Related commands
Related tasks
- List staged changes— Print files (or the full diff) of what's currently in the git index — what `git commit` will commit if you run it now.
- Count files changed since a commit— Get the number of distinct files that differ between a reference commit (a tag, SHA, or `origin/main`) and the working tree — for CI gates and PR-size summaries.
- Show the current git branch— Print the name of the branch HEAD points at — used in shell prompts, CI build labels, and "deploy what's on the branch" scripts.