Repository: pdm-project/pdm

Issue:
`pdm run` conflicts between local file and PATH
- [X ] I have searched the issue tracker and believe that this is not a duplicate.

I believe this is related to https://github.com/pdm-project/pdm/issues/2217

**Make sure you run commands with `-v` flag before pasting the output.**

## Steps to reproduce

```bash
mkdir foo && cd foo
pdm -v init
touch python
pdm -v run python
# [PermissionError]: [Errno 13] Permission denied: '~/foo/python'
```

## Actual behavior

The local file is run.

## Expected behavior

I expect the same behavior as the shell:

- `pdm run python` to run the python executable located at `./.venv/bin/python`
- `pdm run ./python` to run the local file

## Environment Information

```bash
# Paste the output of `pdm info && pdm info --env` below:
PDM version:
  2.9.0
Python Interpreter:
  /home/sdeframond/toto/.venv/bin/python (3.11)
Project Root:
  /home/sdeframond/toto
Local Packages:

{
  "implementation_name": "cpython",
  "implementation_version": "3.11.3",
  "os_name": "posix",
  "platform_machine": "x86_64",
  "platform_release": "5.15.90.1-microsoft-standard-WSL2",
  "platform_system": "Linux",
  "platform_version": "#1 SMP Fri Jan 27 02:56:13 UTC 2023",
  "python_full_version": "3.11.3",
  "platform_python_implementation": "CPython",
  "python_version": "3.11",
  "sys_platform": "linux"
}
```


Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pdm-project/pdm/pull/2223?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) Report
Patch coverage is **`60.00%`** of modified lines.

| [Files Changed](https://app.codecov.io/gh/pdm-project/pdm/pull/2223?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) | Coverage |
|---|---|
| [src/pdm/cli/commands/run.py](https://app.codecov.io/gh/pdm-project/pdm/pull/2223?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project#diff-c3JjL3BkbS9jbGkvY29tbWFuZHMvcnVuLnB5) | `60.00%` |


:loudspeaker: Thoughts on this report? [Let us know!](https://about.codecov.io/pull-request-comment-report/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project).


Patch:
diff --git a/news/2221.bugfix.md b/news/2221.bugfix.md
new file mode 100644
index 00000000..184a8912
--- /dev/null
+++ b/news/2221.bugfix.md
@@ -0,0 +1,1 @@
+`pdm run` should only find local file if the command starts with `./`.
diff --git a/src/pdm/cli/commands/run.py b/src/pdm/cli/commands/run.py
index cd53656e..4e20f5c6 100644
--- a/src/pdm/cli/commands/run.py
+++ b/src/pdm/cli/commands/run.py
@@ -121,8 +121,11 @@ class TaskRunner:
 
     def expand_command(self, command: str) -> str:
         expanded_command = os.path.expanduser(os.path.expandvars(command))
-        if os.path.exists(expanded_command):
-            return os.path.abspath(expanded_command)
+        if expanded_command.replace(os.sep, "/").startswith(("./", "../")):
+            abspath = os.path.abspath(expanded_command)
+            if not os.path.isfile(abspath):
+                raise PdmUsageError(f"Command [success]'{command}'[/] is not a valid executable.")
+            return abspath
         result = self.project.environment.which(command)
         if not result:
             raise PdmUsageError(f"Command [success]'{command}'[/] is not found in your PATH.")


Test patch:
diff --git a/tests/cli/test_run.py b/tests/cli/test_run.py
index 1742d565..4ce9765a 100644
--- a/tests/cli/test_run.py
+++ b/tests/cli/test_run.py
@@ -152,6 +152,13 @@ def test_run_script_with_relative_path(project, pdm, capfd):
     assert out.strip() == "Hello"
 
 
+def test_run_non_existing_local_script(project, pdm):
+    with cd(project.root):
+        result = pdm(["run", "./test_script.sh"], obj=project)
+    assert result.exit_code != 0
+    assert "not a valid executable" in result.stderr
+
+
 @pytest.mark.parametrize(
     "args,expected",
     (