Last active
July 5, 2026 16:08
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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