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.
| # | Bash | PowerShell | Status |
|---|
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 | PowerShell | Notes |
|---|---|---|
| #!/bin/bash | (removed) | Shebang is dropped; PowerShell does not use it. |
| echo "text" | Write-Host "text" | echo -n → Write-Host -NoNewline. |
| echo "t" > f | Set-Content -Path f -Value "t" | >> → Add-Content; other commands → Out-File. |
| ls / ls -la / ls -l | Get-ChildItem / -Force / | Format-Table | -la → -Force; -R → -Recurse. |
| cat f | Get-Content f | Multiple files → comma list. |
| grep "p" f | Select-String -Pattern "p" f | grep -r "p" dir → Get-ChildItem -Path dir -Recurse -File | Select-String. |
| export V=x | $env:V = x | Session-only, like export. |
| V=x | $V = x | Plain 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 / pwd | Set-Location d / Get-Location | Straight renames. |
| cp -r s d / mv s d | Copy-Item -Path s -Destination d -Recurse / Move-Item -Path s -Destination d | |
| rm -rf d | Remove-Item d -Recurse -Force | rm -i → -Confirm. |
| mkdir -p d / touch f | New-Item -ItemType Directory -Path d -Force / New-Item -ItemType File -Path f | |
| if [ -f f ]; then | if (Test-Path f -PathType Leaf) { | -d → -PathType Container; = → -eq; -z → [string]::IsNullOrEmpty. |
| for i in 1 2 3; do | foreach ($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 URL | Invoke-WebRequest -Uri URL -OutFile f | Plain curl URL → Invoke-WebRequest -Uri URL. |
| head -n 5 f / tail -n 5 f | Get-Content f | Select-Object -First 5 / -Last 5 | In a pipeline the Get-Content part is skipped. |
| ... | wc -l | ... | Measure-Object -Line | -w → -Word; -c → -Character. |
| ... | sort | uniq | ... | Sort-Object | Get-Unique | uniq -c → Group-Object | Select-Object Count, Name. |
| ... | cut -d: -f1 | ... | ForEach-Object { $_.Split(':')[0] } | Multi-field -f1,3 supported. |
| a && b / a || b | a; if ($?) { b } / a; if (-not $?) { b } | Portable to Windows PowerShell 5.1 (no native &&). |
| read -p "P: " V | $V = Read-Host "P: " | |
| sleep 5 / date | Start-Sleep -Seconds 5 / Get-Date | |
| diff a b | Compare-Object (Get-Content a) (Get-Content b) | |
| sudo cmd | cmd | sudo 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 | PowerShell | Rule |
|---|---|---|
| echo "Hello, World!" | Write-Host "Hello, World!" | echo → Write-Host |
| ls -la /var/log | Get-ChildItem -Path /var/log -Force | -la → -Force |
| grep -r "TODO" src | Get-ChildItem -Path src -Recurse -File | Select-String -Pattern "TODO" | recursive grep |
| export API_KEY=secret123 | $env:API_KEY = secret123 | export → $env: |
| if [ -f config.yaml ]; then | if (Test-Path config.yaml -PathType Leaf) { | [ -f ] test |
| for f in *.log; do | foreach ($f in *.log) { | for → foreach |
| cat app.log | head -n 20 | Get-Content app.log | Select-Object -First 20 | pipe segment mapping |
| curl -o tool.zip https://example.com/tool.zip | Invoke-WebRequest -Uri https://example.com/tool.zip -OutFile tool.zip | curl -o |
| mkdir -p data/raw && cd data/raw | New-Item -ItemType Directory -Path data/raw -Force; if ($?) { Set-Location data/raw } | && chaining |
| chmod 600 secret.key | # [UNMAPPED] chmod 600 secret.key | honest 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