Using a Unix Workflow on Windows: grep, Pipes, and Scripts in Daily Work
· 27 min read · Views --

Using a Unix Workflow on Windows: grep, Pipes, and Scripts in Daily Work

Author: Alex Xiang


The biggest value of WSL, for me, is not that “Windows can finally run Linux commands.” Git Bash, Cygwin, and MSYS2 could already do part of that.

The real change is that Windows can now support a whole Unix workflow reliably.

Not a few scattered commands, but a chain of search, filtering, transformation, sorting, compression, upload, preview, scheduled tasks, editor, and browser. Windows keeps the desktop efficiency. Linux handles text and automation.

Connecting a Unix command-line workflow through WSL on Windows

This article uses a concrete scenario: late at night, an API error alert arrives. We need to find the endpoints, users, and error causes with the most failures in the last 30 minutes, then turn the result into a Markdown report that can be sent to teammates.

Doing this purely through a log platform UI is tedious. With Unix tools inside WSL, it can become a repeatable script in minutes.

Start with a Plain Tool Box

I do not recommend beginning with complex dotfiles. Install a small, stable set of tools first:

sudo apt update
sudo apt install -y \
  ripgrep \
  jq \
  fzf \
  fd-find \
  bat \
  htop \
  tree \
  make \
  shellcheck \
  rsync \
  moreutils

On Ubuntu, fd may be installed as fdfind. Add a symlink:

mkdir -p ~/.local/bin
ln -sf "$(command -v fdfind)" ~/.local/bin/fd

Confirm ~/.local/bin is in PATH:

echo "$PATH" | tr ':' '\n' | grep "$HOME/.local/bin"

These tools are not exotic. They become powerful in combination:

ToolWhat I mainly use it for
rgQuickly search source code, logs, and Markdown
jqProcess JSON logs, API responses, and config
fzfInteractively choose from many candidates
fdFind files in a more daily-friendly way than find
batView files with syntax highlighting
makeTurn common workflows into stable commands
rsyncSync data and archive results
shellcheckDo basic health checks for shell scripts

Scenario: Turn JSON Logs into a One-page Report

Suppose a log platform exported several JSON Lines files:

logs/
  api-2026-06-21-0000.jsonl
  api-2026-06-21-0010.jsonl
  api-2026-06-21-0020.jsonl

Each line looks roughly like this:

{"ts":"2026-06-21T00:12:03+08:00","path":"/api/v1/search","user_id":"u_123","status":500,"latency_ms":834,"error":"upstream timeout"}

First, do not write Python yet. Explore the data from the command line:

head -n 1 logs/*.jsonl | jq .

Look at recent 5xx errors:

jq -r 'select(.status >= 500) | [.ts, .path, .user_id, .status, .error] | @tsv' logs/*.jsonl | head

Count endpoints with the most errors:

jq -r 'select(.status >= 500) | .path' logs/*.jsonl \
  | sort \
  | uniq -c \
  | sort -nr \
  | head -20

Count error causes:

jq -r 'select(.status >= 500) | .error' logs/*.jsonl \
  | sort \
  | uniq -c \
  | sort -nr \
  | head -20

This is the basic feel of a Unix workflow: each step does one small thing, then passes output to the next.

Analyzing JSON logs with jq, rg, sort, and uniq in WSL, then turning the result into Markdown

The value is not that the commands look clever. The value is that intermediate results are visible. Today you explore the problem with pipes. Tomorrow you move stable steps into a script. The next day the output becomes a Markdown report. Windows handles viewing and sharing; WSL handles slicing the data cleanly.

Collapse the Commands into a Script

After exploring the data, write a script. For example, scripts/analyze-api-errors.sh:

#!/usr/bin/env bash
set -euo pipefail

log_dir="${1:-logs}"
out="${2:-api-error-report.md}"

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

jq -r 'select(.status >= 500) | [.ts, .path, .user_id, .status, .error] | @tsv' "$log_dir"/*.jsonl \
  > "$tmp/errors.tsv"

{
  echo "# API Error Brief"
  echo
  echo "Generated at: $(date '+%Y-%m-%d %H:%M:%S')"
  echo

  echo "## Overview"
  echo
  total="$(wc -l < "$tmp/errors.tsv" | tr -d ' ')"
  echo "- Total 5xx errors: $total"
  echo

  echo "## Top Endpoints"
  echo
  awk -F '\t' '{print $2}' "$tmp/errors.tsv" | sort | uniq -c | sort -nr | head -10 \
    | awk '{count=$1; $1=""; sub(/^ /,""); printf "- %s: %s\n", $0, count}'
  echo

  echo "## Top Error Causes"
  echo
  awk -F '\t' '{print $5}' "$tmp/errors.tsv" | sort | uniq -c | sort -nr | head -10 \
    | awk '{count=$1; $1=""; sub(/^ /,""); printf "- %s: %s\n", $0, count}'
  echo

  echo "## Samples"
  echo
  echo '```text'
  head -20 "$tmp/errors.tsv"
  echo '```'
} > "$out"

echo "$out"

Run:

chmod +x scripts/analyze-api-errors.sh
scripts/analyze-api-errors.sh logs report.md

Open the result from Windows:

explorer.exe .

Or from VS Code:

code report.md

The relationship between WSL and Windows is natural here: scripts run in Linux, Markdown is viewed in Windows or VS Code, and the result can be pasted into a chat, email, or issue.

Use Makefile to Stabilize Entry Points

In a team, “run this script, but with these parameters” gets messy quickly. I usually put common commands into a Makefile:

.PHONY: error-report shellcheck

LOG_DIR ?= logs
REPORT ?= report.md

error-report:
	./scripts/analyze-api-errors.sh "$(LOG_DIR)" "$(REPORT)"

shellcheck:
	shellcheck scripts/*.sh

Usage:

make error-report LOG_DIR=logs REPORT=report.md
make shellcheck

Makefile is not fancy, but it stabilizes the entry point. New teammates do not need to remember complex parameters, and CI can reuse the same command easily.

fzf Makes the Command Line Less Rigid

Sometimes you do not know which log file to inspect. Use fzf:

file="$(fd '\.jsonl$' logs | fzf --prompt='log> ')"
bat "$file"

Or choose an endpoint:

path="$(
  jq -r '.path' logs/*.jsonl \
    | sort -u \
    | fzf --prompt='path> '
)"

jq --arg path "$path" 'select(.path == $path)' logs/*.jsonl | less

fzf puts “let me see the candidates first” back into the command line. Not everything needs a Web UI.

Using fzf in the terminal to choose log files and API paths interactively

Many investigations are not one command. They are “choose a file, choose a path, inspect the result.” fzf fills that semi-interactive gap. It is lighter than a temporary Web page and steadier than copying filenames by hand.

Work with the Windows Desktop, Not Against It

I no longer try to stay in the terminal for everything. Use Windows where Windows is useful.

Open the current directory:

explorer.exe .

Read from the Windows clipboard:

powershell.exe -Command "Get-Clipboard"

Copy a report to the Windows clipboard:

clip.exe < report.md

Open a link with the default browser:

start.exe https://example.com

These commands are good in personal scripts. They should not appear everywhere in team build scripts. Team scripts should usually remain runnable on Linux. Personal productivity scripts can make full use of the Windows desktop.

Scheduled Tasks: Choose the Simple Option First

If a script only needs to run on your machine every morning, do not start with complicated scheduling.

Option one: use Windows Task Scheduler to call WSL:

wsl.exe -d Ubuntu -- bash -lc "cd ~/work/tools && ./scripts/daily-report.sh"

Option two: use a systemd timer inside WSL. This fits when the related services and files all live inside WSL.

My rule:

  • if the task needs Windows login state, desktop notifications, or enterprise desktop tools, use Windows Task Scheduler;
  • if the task is pure Linux scripts and Linux files, use a systemd timer;
  • if the task matters to a team, do not keep it on a personal computer; move it to a server or CI.

A Rule I Have Used for Years

If one command expresses it clearly, do not write a program yet.
If a command repeats more than three times, write a script.
If more than two people use the script, add a Makefile.
If the script affects team delivery, add tests and CI.
If it must run for a long time, move it off your machine.

This is where WSL’s meaning becomes clear: it lets Windows users follow the same Unix evolution path.

Not because terminals are cooler, but because composable text tools genuinely save time.

Summary

One reason I like WSL is that it does not force me to choose between Windows and Linux.

Desktop, browser, communication tools, and Office stay on Windows.
Search, filtering, batch work, builds, tests, and scripts stay in Linux.
When the two sides need to hand things over, small bridges such as explorer.exe, clip.exe, and code . are enough.

Over time, the workflow becomes smooth:

export logs, pipe them, generate a report, copy it out.
pull code, run tests, fix a script, open the result.
download assets, batch transform, preview, archive.

This is not nostalgic Unix romanticism. It is just a way to combine small tools and squeeze out repetitive work.

References