diff --git a/changelog.d/1293.bugfix.md b/changelog.d/1293.bugfix.md new file mode 100644 index 0000000000..f628fdf01f --- /dev/null +++ b/changelog.d/1293.bugfix.md @@ -0,0 +1 @@ +Fix run command with bash substitution (e.g. `pipx run <(pbpaste)`) diff --git a/src/pipx/commands/run.py b/src/pipx/commands/run.py index 78706918f5..c6bb755f7c 100644 --- a/src/pipx/commands/run.py +++ b/src/pipx/commands/run.py @@ -50,7 +50,11 @@ def maybe_script_content(app: str, is_path: bool) -> Optional[Union[str, Path]]: app_path = Path(app) if app_path.is_file(): return app_path - elif is_path: + # In case it's a named pipe, read it out to pass to the interpreter + if app_path.is_fifo(): + return app_path.read_text(encoding="utf-8") + + if is_path: raise PipxError(f"The specified path {app} does not exist") # Check for a URL diff --git a/tests/test_run.py b/tests/test_run.py index 564acc6076..dc25a023bd 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -370,6 +370,35 @@ def test_run_script_by_relative_name(caplog, pipx_temp_env, monkeypatch, tmp_pat assert out.read_text() == test_str +@mock.patch("os.execvpe", new=execvpe_mock) +@pytest.mark.skipif(sys.platform.startswith("win"), reason="uses file descriptor") +def test_run_script_by_file_descriptor(caplog, pipx_temp_env, monkeypatch, tmp_path): + read_fd, write_fd = os.pipe() + out = tmp_path / "output.txt" + test_str = "Hello, world!" + + os.write( + write_fd, + textwrap.dedent( + f""" + from pathlib import Path + Path({repr(str(out))}).write_text({repr(test_str)}) + """ + ) + .strip() + .encode("utf-8"), + ) + os.close(write_fd) + + with monkeypatch.context() as m: + m.chdir(tmp_path) + try: + run_pipx_cli_exit(["run", f"/dev/fd/{read_fd}"]) + finally: + os.close(read_fd) + assert out.read_text() == test_str + + @pytest.mark.skipif(not sys.platform.startswith("win"), reason="uses windows version format") @mock.patch("os.execvpe", new=execvpe_mock) def test_run_with_windows_python_version(caplog, pipx_temp_env, tmp_path):