Repository: microsoft/debugpy

Issue:
Shell expansion is not working in "args"
## Environment data

Version: 1.43.1
Commit: fe22a9645b44368865c0ba92e2fb881ff1afce94
Date: 2020-03-18T07:17:38.324Z
Electron: 7.1.11
Chrome: 78.0.3904.130
Node.js: 12.8.1
V8: 7.8.279.23-electron.0
OS: Linux x64 5.5.8-1-MANJARO

## Expected behaviour

On launch of a run configuration, environment variable calls like $(XXX) or ${XXX} defined in the args section are escaped by putting it between quotation marks.

So if for instance `--arg-xyz=$(XXX)` is put into the args section and should evaluate into 

`--arg-xyz=WHATEVER_IS_THE_OUTPUT_OF_THE_CALL`

## Actual behaviour

`--arg-xyz=$(XXX)` is evaluated to `--arg-xyz="$(XXX)"` what causes the python program launch to fail.

## Steps to reproduce:

Actually I don't know, I used this configuration since months and today it did stop working by some kind of escaping these evaluations by putting it just into the quotation marks.

So the launch.json config:

```
{
            "name": "Odoo",
            "type": "python",
            "request": "launch",
            "program": "/odoo/odoo-bin",
            "args": [
                "--config",
                "/workspace/.devcontainer/etc/odoo.conf",
                "--addons-path",
                "$(gen_addons_path -d /addons)",       <<<<< THIS IS EVALUATED WRONG!
                "--load",
                "base,web,base_test_db",
                "--dev=xml,qweb",      
            ],
            "console": "integratedTerminal",
            "justMyCode": false
        }
```

will result in this actual call of the program:

```
env DEBUGPY_LAUNCHER_PORT=40595 /usr/bin/python3 /var/lib/odoo/.vscode-server/extensions/ms-python.python-2020.3.69010/pythonFiles/lib/python/debugpy/wheels/debugpy/launcher /odoo/odoo-bin --config /workspace/.devcontainer/etc/odoo.conf --addons-path "$(gen_addons_path -d /addons)" --load base,web,base_test_db --dev=xml,qweb 
Usage: odoo-bin [options]

odoo-bin: error: option --addons-path: no such directory: '$(gen_addons_path -d /addons)'
```

As you see here, $(gen_addons_path -d /addons) is not evaluated, but just put in a string.

I would be very grateful for any hints to get this working again...


Thanks in advance!

Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='95.4%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list) [95.4% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='95.4%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list) [95.4% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='95.4%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list) [95.4% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='95.4%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list) [95.4% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='95.4%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list) [95.4% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='95.4%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list) [95.4% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 6a535701..eacf2a46 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,50 +5,71 @@
     "version": "0.2.0",
     "configurations": [
         {
-            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
-            "name": "Launch Python file",
+            "name": "Launch: program",
             "type": "python",
             "request": "launch",
             "console": "integratedTerminal",
-            "consoleTitle": "ptvsd.server",
             "program": "${file}",
             "logToFile": true,
+            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
         },
         {
+            "name": "Launch: module",
+            "type": "python",
+            "request": "launch",
+            "console": "integratedTerminal",
+            "module": "${fileBasenameNoExtension}",
+            "cwd": "${fileDirname}",
+            "logToFile": true,
+            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
+        },
+        {
+            "name": "Launch: code",
+            "type": "python",
+            "request": "launch",
+            "console": "integratedTerminal",
+            "code": [
+                "import runpy",
+                "runpy.run_path(r'${file}')",
+            ],
+            "logToFile": true,
             "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
+        },
+        {
             "name": "Attach: connect",
             "type": "python",
             "request": "attach",
-            "logToFile": true,
             "connect": {
                 "port": 5678,
                 "host": "127.0.0.1",
-            }
+            },
+            "logToFile": true,
+            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
         },
         {
-            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
             "name": "Attach: listen",
             "type": "python",
             "request": "attach",
-            "logToFile": true,
             "listen": {
                 "port": 5678,
                 "host": "127.0.0.1",
-            }
+            },
+            "logToFile": true,
+            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
         },
         {
-            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
             "name": "Attach: PID",
             "type": "python",
             "request": "attach",
-            "processId": "${command:pickProcess}"
+            "processId": "${command:pickProcess}",
+            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
         },
         {
-            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
             "name": "Debug Tests",
             "type": "python",
             "request": "test",
             "console": "integratedTerminal",
+            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
         }
     ]
 }
\ No newline at end of file
diff --git a/src/debugpy/adapter/clients.py b/src/debugpy/adapter/clients.py
index d1ad18e0..a69a9161 100644
--- a/src/debugpy/adapter/clients.py
+++ b/src/debugpy/adapter/clients.py
@@ -8,7 +8,7 @@ import os
 import sys
 
 import debugpy
-from debugpy import adapter
+from debugpy import adapter, launcher
 from debugpy.common import fmt, json, log, messaging, sockets
 from debugpy.common.compat import unicode
 from debugpy.adapter import components, servers, sessions
@@ -272,22 +272,41 @@ class Client(components.Component):
 
             return value
 
-        # Launcher doesn't use the command line at all, but we pass the arguments so
-        # that they show up in the terminal if we're using "runInTerminal".
+        # Propagate command line arguments via launcher CLI rather than "args", so that
+        # they get shell expansion applied to them in "runInTerminal" scenarios.
+
+        program = module = code = ()
         if "program" in request:
-            args = request("program", json.array(unicode, vectorize=True, size=(1,)))
-        elif "module" in request:
-            args = ["-m"] + request(
-                "module", json.array(unicode, vectorize=True, size=(1,))
+            program = request("program", unicode)
+            args = [program]
+            # process_name = program
+        if "module" in request:
+            module = request("module", unicode)
+            args = ["-m", module]
+            # process_name = module
+        if "code" in request:
+            code = request("code", json.array(unicode, vectorize=True, size=(1,)))
+            args = ["-c", "\n".join(code)]
+            # process_name = cmdline[0]
+
+        num_targets = len([x for x in (program, module, code) if x != ()])
+        if num_targets == 0:
+            raise request.isnt_valid(
+                'either "program", "module", or "code" must be specified'
             )
-        elif "code" in request:
-            args = ["-c"] + request(
-                "code", json.array(unicode, vectorize=True, size=(1,))
+        elif num_targets != 1:
+            raise request.isnt_valid(
+                '"program", "module", and "code" are mutually exclusive'
             )
-        else:
-            args = []
+
         args += request("args", json.array(unicode))
 
+        cwd = request("cwd", unicode, optional=True)
+        if cwd == ():
+            # If it's not specified, but we're launching a file rather than a module,
+            # and the specified path has a directory in it, use that.
+            cwd = None if program == () else (os.path.dirname(program) or None)
+
         console = request(
             "console",
             json.enum(
@@ -303,9 +322,18 @@ class Client(components.Component):
         if sudo and sys.platform == "win32":
             raise request.cant_handle('"sudo":true is not supported on Windows.')
 
+        launcher_path = request("debugLauncherPath", os.path.dirname(launcher.__file__))
+
         servers.serve()
         launchers.spawn_debuggee(
-            self.session, request, args, console, console_title, sudo
+            self.session,
+            request,
+            launcher_path,
+            args,
+            cwd,
+            console,
+            console_title,
+            sudo,
         )
 
     @_start_message_handler
diff --git a/src/debugpy/adapter/launchers.py b/src/debugpy/adapter/launchers.py
index 1a91f4b3..ce502fb8 100644
--- a/src/debugpy/adapter/launchers.py
+++ b/src/debugpy/adapter/launchers.py
@@ -8,7 +8,7 @@ import os
 import subprocess
 import sys
 
-from debugpy import adapter, launcher
+from debugpy import adapter
 from debugpy.common import compat, log, messaging, sockets
 from debugpy.adapter import components, servers
 
@@ -65,13 +65,13 @@ class Launcher(components.Component):
                     pass
 
 
-def spawn_debuggee(session, start_request, args, console, console_title, sudo):
+def spawn_debuggee(
+    session, start_request, launcher_path, args, cwd, console, console_title, sudo
+):
     # -E tells sudo to propagate environment variables to the target process - this
     # is necessary for launcher to get DEBUGPY_LAUNCHER_PORT and DEBUGPY_LOG_DIR.
     cmdline = ["sudo", "-E"] if sudo else []
-
-    cmdline += [sys.executable, os.path.dirname(launcher.__file__)]
-    cmdline += args
+    cmdline += [sys.executable, launcher_path]
     env = {}
 
     arguments = dict(start_request.arguments)
@@ -90,15 +90,14 @@ def spawn_debuggee(session, start_request, args, console, console_title, sudo):
         )
     except Exception as exc:
         raise start_request.cant_handle(
-            "{0} couldn't create listener socket for launcher: {1}",
-            session,
-            exc,
+            "{0} couldn't create listener socket for launcher: {1}", session, exc
         )
 
     try:
         _, launcher_port = listener.getsockname()
+        cmdline += [str(launcher_port), "--"]
+        cmdline += args
 
-        env[str("DEBUGPY_LAUNCHER_PORT")] = str(launcher_port)
         if log.log_dir is not None:
             env[str("DEBUGPY_LOG_DIR")] = compat.filename_str(log.log_dir)
         if log.stderr.levels != {"warning", "error"}:
@@ -107,11 +106,20 @@ def spawn_debuggee(session, start_request, args, console, console_title, sudo):
         if console == "internalConsole":
             log.info("{0} spawning launcher: {1!r}", session, cmdline)
             try:
+                for i, arg in enumerate(cmdline):
+                    try:
+                        cmdline[i] = compat.filename_str(arg)
+                    except UnicodeEncodeError as exc:
+                        raise start_request.cant_handle(
+                            "Invalid command line argument {0!j}: {1}", arg, exc
+                        )
+
                 # If we are talking to the client over stdio, sys.stdin and sys.stdout
                 # are redirected to avoid mangling the DAP message stream. Make sure
                 # the launcher also respects that.
                 subprocess.Popen(
                     cmdline,
+                    cwd=cwd,
                     env=dict(list(os.environ.items()) + list(env.items())),
                     stdin=sys.stdin,
                     stdout=sys.stdout,
@@ -123,22 +131,24 @@ def spawn_debuggee(session, start_request, args, console, console_title, sudo):
             log.info('{0} spawning launcher via "runInTerminal" request.', session)
             session.client.capabilities.require("supportsRunInTerminalRequest")
             kinds = {"integratedTerminal": "integrated", "externalTerminal": "external"}
+            request_args = {
+                "kind": kinds[console],
+                "title": console_title,
+                "args": cmdline,
+                "env": env,
+            }
+            if cwd is not None:
+                request_args["cwd"] = cwd
             try:
-                session.client.channel.request(
-                    "runInTerminal",
-                    {
-                        "kind": kinds[console],
-                        "title": console_title,
-                        "args": cmdline,
-                        "env": env,
-                    },
-                )
+                session.client.channel.request("runInTerminal", request_args)
             except messaging.MessageHandlingError as exc:
                 exc.propagate(start_request)
 
         # If using sudo, it might prompt for password, and launcher won't start running
         # until the user enters it, so don't apply timeout in that case.
-        if not session.wait_for(lambda: session.launcher, timeout=(None if sudo else 10)):
+        if not session.wait_for(
+            lambda: session.launcher, timeout=(None if sudo else 10)
+        ):
             raise start_request.cant_handle("Timed out waiting for launcher to connect")
 
         try:
diff --git a/src/debugpy/launcher/__init__.py b/src/debugpy/launcher/__init__.py
index 4ec59ac5..14b7a40b 100644
--- a/src/debugpy/launcher/__init__.py
+++ b/src/debugpy/launcher/__init__.py
@@ -16,7 +16,7 @@ channel = None
 """DAP message channel to the adapter."""
 
 
-def connect(launcher_port):
+def connect(host, port):
     from debugpy.common import messaging, sockets
     from debugpy.launcher import handlers
 
@@ -24,7 +24,7 @@ def connect(launcher_port):
     assert channel is None
 
     sock = sockets.create_client()
-    sock.connect(("127.0.0.1", launcher_port))
+    sock.connect((host, port))
 
     stream = messaging.JsonIOStream.from_socket(sock, "Adapter")
     channel = messaging.JsonMessageChannel(stream, handlers=handlers)
diff --git a/src/debugpy/launcher/__main__.py b/src/debugpy/launcher/__main__.py
index 4866caa5..482ef4e2 100644
--- a/src/debugpy/launcher/__main__.py
+++ b/src/debugpy/launcher/__main__.py
@@ -31,15 +31,22 @@ def main():
     # will also exit, so it doesn't need to observe the signal directly.
     signal.signal(signal.SIGINT, signal.SIG_IGN)
 
-    def option(name, type, *args):
-        try:
-            return type(os.environ.pop(name, *args))
-        except Exception:
-            log.reraise_exception("Error parsing {0!r}:", name)
-
-    launcher_port = option("DEBUGPY_LAUNCHER_PORT", int)
-
-    launcher.connect(launcher_port)
+    # Everything before "--" is command line arguments for the launcher itself,
+    # and everything after "--" is command line arguments for the debuggee.
+    sep = sys.argv.index("--")
+    launcher_argv = sys.argv[1:sep]
+    sys.argv = [sys.argv[0]] + sys.argv[sep + 1:]
+
+    # The first argument specifies the host/port on which the adapter is waiting
+    # for launcher to connect. It's either host:port, or just port.
+    adapter = launcher_argv[0]
+    host, sep, port = adapter.partition(":")
+    if not sep:
+        host = "127.0.0.1"
+        port = adapter
+    port = int(port)
+
+    launcher.connect(host, port)
     launcher.channel.wait()
 
     if debuggee.process is not None:
diff --git a/src/debugpy/launcher/debuggee.py b/src/debugpy/launcher/debuggee.py
index 9290a786..fc0b1c0c 100644
--- a/src/debugpy/launcher/debuggee.py
+++ b/src/debugpy/launcher/debuggee.py
@@ -31,13 +31,11 @@ def describe():
     return fmt("Debuggee[PID={0}]", process.pid)
 
 
-def spawn(process_name, cmdline, cwd, env, redirect_output):
+def spawn(process_name, cmdline, env, redirect_output):
     log.info(
         "Spawning debuggee process:\n\n"
-        "Current directory: {0!r}\n\n"
-        "Command line: {1!r}\n\n"
-        "Environment variables: {2!r}\n\n",
-        cwd,
+        "Command line: {0!r}\n\n"
+        "Environment variables: {1!r}\n\n",
         cmdline,
         env,
     )
@@ -56,7 +54,7 @@ def spawn(process_name, cmdline, cwd, env, redirect_output):
 
         try:
             global process
-            process = subprocess.Popen(cmdline, cwd=cwd, env=env, bufsize=0, **kwargs)
+            process = subprocess.Popen(cmdline, env=env, bufsize=0, **kwargs)
         except Exception as exc:
             raise messaging.MessageHandlingError(
                 fmt("Couldn't spawn debuggee: {0}\n\nCommand line:{1!r}", exc, cmdline)
diff --git a/src/debugpy/launcher/handlers.py b/src/debugpy/launcher/handlers.py
index b353a24c..811bc554 100644
--- a/src/debugpy/launcher/handlers.py
+++ b/src/debugpy/launcher/handlers.py
@@ -67,37 +67,14 @@ def launch_request(request):
         debugpy_args = request("debugpyArgs", json.array(unicode))
         cmdline += debugpy_args
 
-    program = module = code = ()
-    if "program" in request:
-        program = request("program", unicode)
-        cmdline += [program]
-        process_name = program
-    if "module" in request:
-        module = request("module", unicode)
-        cmdline += ["-m", module]
-        process_name = module
-    if "code" in request:
-        code = request("code", json.array(unicode, vectorize=True, size=(1,)))
-        cmdline += ["-c", "\n".join(code)]
-        process_name = cmdline[0]
-
-    num_targets = len([x for x in (program, module, code) if x != ()])
-    if num_targets == 0:
-        raise request.isnt_valid(
-            'either "program", "module", or "code" must be specified'
-        )
-    elif num_targets != 1:
-        raise request.isnt_valid(
-            '"program", "module", and "code" are mutually exclusive'
-        )
-
-    cmdline += request("args", json.array(unicode))
-
-    cwd = request("cwd", unicode, optional=True)
-    if cwd == ():
-        # If it's not specified, but we're launching a file rather than a module,
-        # and the specified path has a directory in it, use that.
-        cwd = None if program == () else (os.path.dirname(program[0]) or None)
+    # Use command line arguments propagated via launcher CLI, rather than "args", to get
+    # their values after shell expansion in "runInTerminal" scenarios. The command line
+    # parser in __main__ has already removed everything up to and including "--" by now.
+    cmdline += sys.argv[1:]
+
+    # We want the process name to reflect the target. So for -m, use the module name
+    # that follows; otherwise use the first argument, which is either -c or filename.
+    process_name = compat.filename(sys.argv[1] if sys.argv[1] != "-m" else sys.argv[2])
 
     env = os.environ.copy()
     env_changes = request("env", json.object(unicode))
@@ -161,7 +138,7 @@ def launch_request(request):
         cmdline = [encode(s) for s in cmdline]
         env = {encode(k): encode(v) for k, v in env.items()}
 
-    debuggee.spawn(process_name, cmdline, cwd, env, redirect_output)
+    debuggee.spawn(process_name, cmdline, env, redirect_output)
     return {}
 
 


Test patch:
diff --git a/tests/debug/__init__.py b/tests/debug/__init__.py
index 5e5995d6..d721c7b6 100644
--- a/tests/debug/__init__.py
+++ b/tests/debug/__init__.py
@@ -4,9 +4,5 @@
 
 from __future__ import absolute_import, division, print_function, unicode_literals
 
-
 # Expose Session directly.
-def Session(*args, **kwargs):
-    from tests.debug import session
-
-    return session.Session(*args, **kwargs)
+from tests.debug.session import Session  # noqa
diff --git a/tests/debug/session.py b/tests/debug/session.py
index 2081ee6e..2b391063 100644
--- a/tests/debug/session.py
+++ b/tests/debug/session.py
@@ -484,6 +484,12 @@ class Session(object):
                 pid, fmt("{0}-subprocess-{1}", self.debuggee_id, pid)
             )
 
+    def run_in_terminal(self, args, cwd, env):
+        exe = args.pop(0)
+        self.spawn_debuggee.env.update(env)
+        self.spawn_debuggee(args, cwd, exe=exe)
+        return {}
+
     def _process_request(self, request):
         self.timeline.record_request(request, block=False)
         if request.command == "runInTerminal":
@@ -491,11 +497,8 @@ class Session(object):
             cwd = request("cwd", ".")
             env = request("env", json.object(unicode))
             try:
-                exe = args.pop(0)
-                self.spawn_debuggee.env.update(env)
-                self.spawn_debuggee(args, cwd, exe=exe)
-                return {}
-            except OSError as exc:
+                return self.run_in_terminal(args, cwd, env)
+            except Exception as exc:
                 log.swallow_exception('"runInTerminal" failed:')
                 raise request.cant_handle(str(exc))
         else:
diff --git a/tests/debugpy/test_args.py b/tests/debugpy/test_args.py
index 6a74b97f..d8d728de 100644
--- a/tests/debugpy/test_args.py
+++ b/tests/debugpy/test_args.py
@@ -31,3 +31,37 @@ def test_args(pyfile, target, run):
             pass
         argv = backchannel.receive()
         assert argv == [some.str] + args
+
+
+@pytest.mark.parametrize("target", targets.all)
+@pytest.mark.parametrize("run", runners.all_launch)
+def test_shell_expansion(pyfile, target, run):
+    @pyfile
+    def code_to_debug():
+        import sys
+        import debuggee
+        from debuggee import backchannel
+
+        debuggee.setup()
+        backchannel.send(sys.argv)
+
+    def expand(args):
+        for i, arg in enumerate(args):
+            if arg.startswith("$"):
+                args[i] = arg[1:]
+
+    class Session(debug.Session):
+        def run_in_terminal(self, args, cwd, env):
+            expand(args)
+            return super(Session, self).run_in_terminal(args, cwd, env)
+
+    args = ["0", "$1", "2"]
+    with Session() as session:
+        backchannel = session.open_backchannel()
+        with run(session, target(code_to_debug, args=args)):
+            pass
+        argv = backchannel.receive()
+
+    if session.config["console"] != "internalConsole":
+        expand(args)
+    assert argv == [some.str] + args