Skip to content

Instantly share code, notes, and snippets.

@dansp89
Last active July 5, 2026 16:08
Show Gist options
  • Select an option

  • Save dansp89/63f387493d628ae95bb86ae1c0286523 to your computer and use it in GitHub Desktop.

Select an option

Save dansp89/63f387493d628ae95bb86ae1c0286523 to your computer and use it in GitHub Desktop.
c: kill TCP ports and wait until they are free (cross-platform: bash/powershell/cmd)
<#
c - kill whatever is using one or more TCP ports and wait until each is free.
Usage: c <port1> [port2] [portN...]
Internal timeout: 10s per port.
#>
param([Parameter(ValueFromRemainingArguments = $true)] [string[]] $Ports)
if (-not $Ports -or $Ports.Count -eq 0) {
Write-Host "Usage: c <port1> [port2] [portN...]"
exit 1
}
$timeout = 10
$rc = 0
foreach ($port in $Ports) {
if ($port -notmatch '^\d+$') { Write-Warning "Skipping '$port' (not a port)"; continue }
Write-Host "Waiting for port $port to close..."
$start = Get-Date
while ($true) {
$procIds = @()
try {
$procIds = Get-NetTCPConnection -LocalPort $port -ErrorAction Stop |
Select-Object -ExpandProperty OwningProcess -Unique
} catch {
# Fallback via netstat (environments without the NetTCPIP module)
$procIds = @(netstat -ano | Select-String ":$port\s" |
ForEach-Object { ($_ -split '\s+')[-1] } | Sort-Object -Unique)
}
$procIds = $procIds | Where-Object { $_ -and $_ -ne 0 }
if (-not $procIds) { Write-Host "OK: port $port is free!"; break }
foreach ($p in $procIds) { Stop-Process -Id $p -Force -ErrorAction SilentlyContinue }
if (((Get-Date) - $start).TotalSeconds -ge $timeout) {
Write-Warning "Timeout: port $port still in use after ${timeout}s"
$rc = 1
break
}
Start-Sleep -Milliseconds 200
}
}
exit $rc
# c: kill whatever is using one or more TCP ports and wait until each is free.
# Usage: c <port1> [port2] [portN...] (internal timeout: 10s per port)
c() {
if [ $# -eq 0 ]; then
echo "Usage: c <port1> [port2] [portN...]"
return 1
fi
local timeout=10 rc=0
for port in "$@"; do
case "$port" in
''|*[!0-9]*) echo "Skipping '$port' (not a port)"; continue ;;
esac
echo "Waiting for port $port to close..."
local start=$(date +%s)
while true; do
local pids=""
if command -v lsof >/dev/null 2>&1; then
pids=$(lsof -ti :"$port" 2>/dev/null)
else
pids=$(netstat -ano 2>/dev/null | awk -v p=":$port$" '$2 ~ p {print $NF}' | sort -u)
fi
if [ -z "$pids" ]; then
echo "OK: port $port is free!"
break
fi
for pid in $pids; do
[ "$pid" = "0" ] && continue
if command -v taskkill >/dev/null 2>&1; then
MSYS_NO_PATHCONV=1 taskkill /F /PID "$pid" >/dev/null 2>&1
else
kill -9 "$pid" 2>/dev/null
fi
done
if [ $(( $(date +%s) - start )) -ge $timeout ]; then
echo "Timeout: port $port still in use after ${timeout}s"
rc=1
break
fi
sleep 0.2
done
done
return $rc
}
# Installer for the `c` command on Windows PowerShell.
# irm https://gist.githubusercontent.com/dansp89/63f387493d628ae95bb86ae1c0286523/raw/install.ps1 | iex
# Idempotent. Installs the engine into ~\.local\bin, a CMD wrapper and a profile function.
$ErrorActionPreference = 'Stop'
$bin = Join-Path $HOME '.local\bin'
New-Item -ItemType Directory -Path $bin -Force | Out-Null
# ── Engine c.ps1 ──────────────────────────────────────────────────────────────
$engine = @'
param([Parameter(ValueFromRemainingArguments = $true)] [string[]] $Ports)
if (-not $Ports -or $Ports.Count -eq 0) { Write-Host "Usage: c <port1> [port2] [portN...]"; exit 1 }
$timeout = 10; $rc = 0
foreach ($port in $Ports) {
if ($port -notmatch '^\d+$') { Write-Warning "Skipping '$port' (not a port)"; continue }
Write-Host "Waiting for port $port to close..."
$start = Get-Date
while ($true) {
$procIds = @()
try {
$procIds = Get-NetTCPConnection -LocalPort $port -ErrorAction Stop |
Select-Object -ExpandProperty OwningProcess -Unique
} catch {
$procIds = @(netstat -ano | Select-String ":$port\s" |
ForEach-Object { ($_ -split '\s+')[-1] } | Sort-Object -Unique)
}
$procIds = $procIds | Where-Object { $_ -and $_ -ne 0 }
if (-not $procIds) { Write-Host "OK: port $port is free!"; break }
foreach ($p in $procIds) { Stop-Process -Id $p -Force -ErrorAction SilentlyContinue }
if (((Get-Date) - $start).TotalSeconds -ge $timeout) {
Write-Warning "Timeout: port $port still in use after ${timeout}s"; $rc = 1; break
}
Start-Sleep -Milliseconds 200
}
}
exit $rc
'@
Set-Content -Path (Join-Path $bin 'c.ps1') -Value $engine -Encoding utf8
Write-Host " -> $bin\c.ps1"
# ── CMD wrapper (plain ASCII, no <> in comments) ─────────────────────────────
$cmd = "@echo off`r`nrem c - kill TCP ports and wait until they are free.`r`npowershell -NoProfile -ExecutionPolicy RemoteSigned -File `"%~dp0c.ps1`" %*`r`n"
Set-Content -Path (Join-Path $bin 'c.cmd') -Value $cmd -Encoding ascii -NoNewline
Write-Host " -> $bin\c.cmd"
# ── User PATH (so CMD can find c.cmd) ────────────────────────────────────────
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if (($userPath -split ';') -notcontains $bin) {
[Environment]::SetEnvironmentVariable('Path', ($userPath.TrimEnd(';') + ';' + $bin), 'User')
Write-Host " -> added to user PATH: $bin"
}
# ── PowerShell profile function (idempotent) ─────────────────────────────────
$profileDir = Split-Path $PROFILE.CurrentUserAllHosts
if (-not (Test-Path $profileDir)) { New-Item -ItemType Directory -Path $profileDir -Force | Out-Null }
$profilePath = $PROFILE.CurrentUserAllHosts
$hasFunc = (Test-Path $profilePath) -and (Select-String -Path $profilePath -Pattern 'function c ' -Quiet)
if (-not $hasFunc) {
Add-Content -Path $profilePath -Value "`n# c-killport`nfunction c { & `"`$HOME\.local\bin\c.ps1`" @args }" -Encoding utf8
Write-Host " -> function added to $profilePath"
}
Write-Host ""
Write-Host "Done! Open a new terminal (or run: . `$PROFILE)."
Write-Host "Usage: c 3000 8080 8473"
#!/usr/bin/env bash
# Installer for the `c` command (kill TCP ports and wait until they are free).
# curl -fsSL https://gist.githubusercontent.com/dansp89/63f387493d628ae95bb86ae1c0286523/raw/install.sh | bash
# Idempotent and cross-platform (Linux, macOS, Git Bash).
set -e
SHARE="$HOME/.local/share/c-killport"
BIN="$HOME/.local/bin"
mkdir -p "$SHARE" "$BIN"
MARKER="# >>> c-killport (source) >>>"
# ── Write the bash function (single-quoted heredoc = no escaping) ─────────────
cat > "$SHARE/c.sh" <<'CSH_EOF'
# c: kill whatever is using one or more TCP ports and wait until each is free.
# Usage: c <port1> [port2] [portN...] (internal timeout: 10s per port)
c() {
if [ $# -eq 0 ]; then
echo "Usage: c <port1> [port2] [portN...]"
return 1
fi
local timeout=10 rc=0
for port in "$@"; do
case "$port" in
''|*[!0-9]*) echo "Skipping '$port' (not a port)"; continue ;;
esac
echo "Waiting for port $port to close..."
local start=$(date +%s)
while true; do
local pids=""
if command -v lsof >/dev/null 2>&1; then
pids=$(lsof -ti :"$port" 2>/dev/null)
else
pids=$(netstat -ano 2>/dev/null | awk -v p=":$port$" '$2 ~ p {print $NF}' | sort -u)
fi
if [ -z "$pids" ]; then
echo "OK: port $port is free!"
break
fi
for pid in $pids; do
[ "$pid" = "0" ] && continue
if command -v taskkill >/dev/null 2>&1; then
MSYS_NO_PATHCONV=1 taskkill /F /PID "$pid" >/dev/null 2>&1
else
kill -9 "$pid" 2>/dev/null
fi
done
if [ $(( $(date +%s) - start )) -ge $timeout ]; then
echo "Timeout: port $port still in use after ${timeout}s"
rc=1
break
fi
sleep 0.2
done
done
return $rc
}
CSH_EOF
# ── Add a 'source' line to the rc files (marker-guarded, idempotent) ──────────
add_source() {
local rc="$1"
[ -f "$rc" ] || touch "$rc"
grep -qF "$MARKER" "$rc" && return 0
{
echo ""
echo "$MARKER"
echo "[ -f \"$SHARE/c.sh\" ] && . \"$SHARE/c.sh\""
echo "# <<< c-killport (source) <<<"
} >> "$rc"
echo " -> $rc"
}
echo "Installing the 'c' command..."
add_source "$HOME/.bashrc"
[ -f "$HOME/.zshrc" ] && add_source "$HOME/.zshrc"
# ── Bonus on Windows/Git Bash: also install for PowerShell and CMD ───────────
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*)
echo "Windows detected — also installing for PowerShell and CMD..."
# engine c.ps1
curl -fsSL "https://gist.githubusercontent.com/dansp89/63f387493d628ae95bb86ae1c0286523/raw/c.ps1" -o "$BIN/c.ps1" 2>/dev/null || true
# c.cmd wrapper
printf '@echo off\r\nrem c - kill TCP ports and wait until they are free.\r\npowershell -NoProfile -ExecutionPolicy RemoteSigned -File "%%~dp0c.ps1" %%*\r\n' > "$BIN/c.cmd"
echo " -> $BIN/c.cmd (make sure $BIN is on your PATH)"
;;
esac
echo ""
echo "Done! Open a new terminal or run: source ~/.bashrc"
echo "Usage: c 3000 8080 8473"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment