Skip to main content

Bash to PowerShell Converter

Paste a bash script and get a best-effort PowerShell translation: echo to Write-Host, ls to Get-ChildItem, grep to Select-String, export to $env:, if [ ] to if (), for-loops to foreach, and more. Every line is flagged MAPPED or UNMAPPED — unmapped lines are emitted as comments so they can never run blindly.

✓ 100% client-side — nothing you paste leaves your browser
⚠ Heuristic mapping, not a transpiler — always review the output

The mapping table

Every rule below is implemented in the converter above — nothing here is aspirational. Constructs with no honest equivalent are flagged UNMAPPED instead of being faked.

Bash construct → PowerShell
BashPowerShellNotes
#!/bin/bash(removed)Shebang is dropped; PowerShell does not use it.
echo "text"Write-Host "text"echo -n → Write-Host -NoNewline.
echo "t" > fSet-Content -Path f -Value "t">> → Add-Content; other commands → Out-File.
ls / ls -la / ls -lGet-ChildItem / -Force / | Format-Table-la → -Force; -R → -Recurse.
cat fGet-Content fMultiple files → comma list.
grep "p" fSelect-String -Pattern "p" fgrep -r "p" dir → Get-ChildItem -Path dir -Recurse -File | Select-String.
export V=x$env:V = xSession-only, like export.
V=x$V = xPlain assignment stays a shell variable.
$HOME / ~/x$HOME / $HOME/x$HOME is automatic in PowerShell too; other UPPERCASE $VAR → $env:VAR (documented heuristic).
cd d / pwdSet-Location d / Get-LocationStraight renames.
cp -r s d / mv s dCopy-Item -Path s -Destination d -Recurse / Move-Item -Path s -Destination d 
rm -rf dRemove-Item d -Recurse -Forcerm -i → -Confirm.
mkdir -p d / touch fNew-Item -ItemType Directory -Path d -Force / New-Item -ItemType File -Path f 
if [ -f f ]; thenif (Test-Path f -PathType Leaf) {-d → -PathType Container; = → -eq; -z → [string]::IsNullOrEmpty.
for i in 1 2 3; doforeach ($i in 1, 2, 3) {done → }; $@ items → $args.
name() { / function n {function name {Bash function syntax normalizes to PowerShell form.
curl -o f URL / wget -O f URLInvoke-WebRequest -Uri URL -OutFile fPlain curl URL → Invoke-WebRequest -Uri URL.
head -n 5 f / tail -n 5 fGet-Content f | Select-Object -First 5 / -Last 5In a pipeline the Get-Content part is skipped.
... | wc -l... | Measure-Object -Line-w → -Word; -c → -Character.
... | sort | uniq... | Sort-Object | Get-Uniqueuniq -c → Group-Object | Select-Object Count, Name.
... | cut -d: -f1... | ForEach-Object { $_.Split(':')[0] }Multi-field -f1,3 supported.
a && b / a || ba; if ($?) { b } / a; if (-not $?) { b }Portable to Windows PowerShell 5.1 (no native &&).
read -p "P: " V$V = Read-Host "P: " 
sleep 5 / dateStart-Sleep -Seconds 5 / Get-Date 
diff a bCompare-Object (Get-Content a) (Get-Content b) 
sudo cmdcmdsudo stripped with a note: run PowerShell elevated instead.
chmod 600 f# [UNMAPPED]No direct equivalent — use icacls or Set-Acl (see FAQ).
$( ) / backticks / $1 / $@# [UNMAPPED]Command substitution and positional/special params need manual rewrite.
case/esac / [[ ]] / (( ))# [UNMAPPED]Flagged honestly; case suggests switch in the note.

Worked examples

Each pair below is produced by the converter itself — paste any input on the left into the tool above and you get the output on the right, including the honest UNMAPPED case.

Bash input → PowerShell output
BashPowerShellRule
echo "Hello, World!"Write-Host "Hello, World!"echo → Write-Host
ls -la /var/logGet-ChildItem -Path /var/log -Force-la → -Force
grep -r "TODO" srcGet-ChildItem -Path src -Recurse -File | Select-String -Pattern "TODO"recursive grep
export API_KEY=secret123$env:API_KEY = secret123export → $env:
if [ -f config.yaml ]; thenif (Test-Path config.yaml -PathType Leaf) {[ -f ] test
for f in *.log; doforeach ($f in *.log) {for → foreach
cat app.log | head -n 20Get-Content app.log | Select-Object -First 20pipe segment mapping
curl -o tool.zip https://example.com/tool.zipInvoke-WebRequest -Uri https://example.com/tool.zip -OutFile tool.zipcurl -o
mkdir -p data/raw && cd data/rawNew-Item -ItemType Directory -Path data/raw -Force; if ($?) { Set-Location data/raw }&& chaining
chmod 600 secret.key# [UNMAPPED] chmod 600 secret.keyhonest flag: no equivalent (icacls/Set-Acl note)

Frequently asked questions

Is this a perfect bash-to-PowerShell transpiler?

No — it is a best-effort heuristic mapper, not a transpiler. It converts line by line using a defined rule table (echo to Write-Host, ls to Get-ChildItem, grep to Select-String, export to $env:, and so on). Every line is flagged MAPPED or UNMAPPED, and unmapped lines are emitted as comments so the script cannot run them blindly. Always review and test the output in a safe environment before running it.

What can this converter NOT convert?

Several bash constructs have no direct PowerShell equivalent and are flagged UNMAPPED: chmod/chown (use icacls or Set-Acl instead), case/esac statements, command substitution $( ) and backticks, positional parameters like $1, $@ and $#, arrays, process substitution <( ), traps, and set -e semantics. Heredocs are also unmapped — rewrite them as PowerShell here-strings (@'...'@).

Why do some lines pass through unchanged?

Commands like git, npm, python3, ssh, and docker run as native commands in PowerShell too, so they are passed through unchanged with a note to verify the command exists on your Windows machine. Only bash-specific syntax (tests, loops, substitutions, builtins) gets rewritten or flagged UNMAPPED.

What are the biggest philosophy differences between bash and PowerShell?

Bash pipes plain text; PowerShell pipes .NET objects, which is why grep becomes Select-String and wc -l becomes Measure-Object -Line. Environment variables move from export VAR=value to $env:VAR = "value". Tests change from [ -f file ] to Test-Path file -PathType Leaf. File permissions are the sharpest break: chmod has no equivalent because Windows uses ACLs (icacls or Set-Acl), so those lines are flagged for manual rewrite.

How are pipes converted?

Common pipe segments have defined mappings: grep becomes Select-String -Pattern, head -n N becomes Select-Object -First N, tail -n N becomes Select-Object -Last N, wc -l becomes Measure-Object -Line, sort becomes Sort-Object, uniq becomes Get-Unique, cut -d/-f becomes a ForEach-Object Split expression, and tee becomes Tee-Object. Each segment is converted independently and re-joined with the PowerShell pipe.

What is the PowerShell equivalent of chmod?

There is no direct equivalent, so chmod lines are flagged UNMAPPED with a note instead of a fake conversion. The usual replacements are icacls (for example icacls file /grant:r username:F) or Set-Acl with a FileSystemAccessRule object. You must adapt the permission model manually — a numeric mode like 600 does not translate one to one into Windows ACLs.

How does it handle && and || chaining?

Native && and || only exist in PowerShell 7 and later. For compatibility the converter rewrites cmd1 && cmd2 as cmd1; if ($?) { cmd2 } and cmd1 || cmd2 as cmd1; if (-not $?) { cmd2 }. The $? automatic variable holds whether the previous command succeeded, which mirrors the bash semantics.

Is my script sent to a server?

No. The conversion runs entirely in JavaScript inside your browser — no line of your script is transmitted anywhere. You can disconnect from the network after the page loads and the converter keeps working.

Can it convert PowerShell back to bash?

No. This tool is strictly one-way: bash in, PowerShell out. The reverse direction is not implemented here and is intentionally out of scope, because the mapping rules are written specifically for bash syntax constructs.

Free tool by NRTechStudio