{"id":13508939,"url":"https://github.com/saleyn/erlexec","last_synced_at":"2025-05-13T20:22:17.416Z","repository":{"id":4849094,"uuid":"6003489","full_name":"saleyn/erlexec","owner":"saleyn","description":"Execute and control OS processes from Erlang/OTP","archived":false,"fork":false,"pushed_at":"2025-03-27T04:05:33.000Z","size":1005,"stargazers_count":560,"open_issues_count":2,"forks_count":143,"subscribers_count":23,"default_branch":"master","last_synced_at":"2025-04-28T11:55:52.884Z","etag":null,"topics":["erlang","erlang-processes","erlexec","linux","os","otp"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/erlexec/readme.html","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"doctrine/dbal","license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saleyn.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.txt","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"saleyn"}},"created_at":"2012-09-28T21:43:26.000Z","updated_at":"2025-04-25T00:45:46.000Z","dependencies_parsed_at":"2024-11-06T00:03:50.049Z","dependency_job_id":"cecd20d7-292e-4f63-b1f8-e8cbfe070f11","html_url":"https://github.com/saleyn/erlexec","commit_stats":{"total_commits":524,"total_committers":39,"mean_commits":"13.435897435897436","dds":0.3435114503816794,"last_synced_commit":"8cf9bb8ac871156ed29971719a20b8db7c580a73"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saleyn%2Ferlexec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saleyn%2Ferlexec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saleyn%2Ferlexec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saleyn%2Ferlexec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saleyn","download_url":"https://codeload.github.com/saleyn/erlexec/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251311332,"owners_count":21569008,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["erlang","erlang-processes","erlexec","linux","os","otp"],"created_at":"2024-08-01T02:01:00.721Z","updated_at":"2025-04-28T11:55:59.435Z","avatar_url":"https://github.com/saleyn.png","language":"C++","funding_links":["https://github.com/sponsors/saleyn"],"categories":["OTP"],"sub_categories":[],"readme":"# Erlexec - OS Process Manager for the Erlang VM\n\n[![build](https://github.com/saleyn/erlexec/actions/workflows/erlang.yml/badge.svg)](https://github.com/saleyn/erlexec/actions/workflows/erlang.yml)\n[![Hex.pm](https://img.shields.io/hexpm/v/erlexec.svg)](https://hex.pm/packages/erlexec)\n[![Hex.pm](https://img.shields.io/hexpm/dt/erlexec.svg)](https://hex.pm/packages/erlexec)\n\n**Author** Serge Aleynikov \u003csaleyn(at)gmail.com\u003e\n\n## Summary ##\n\nExecute and control OS processes from Erlang/OTP.\n\nThis project implements an Erlang application with a C++ port program\nthat gives light-weight Erlang processes fine-grain control over\nexecution of OS processes.\n\nThe following features are supported:\n\n* Start/stop OS commands and get their OS process IDs, and termination reason\n  (exit code, signal number, core dump status).\n* Support OS commands with unicode characters.\n* Manage/monitor externally started OS processes.\n* Execute OS processes synchronously and asynchronously.\n* Set OS command's working directory, environment, process group, effective user, process priority.\n* Provide custom termination command for killing a process or relying on\n  default SIGTERM/SIGKILL behavior.\n* Specify custom timeout for SIGKILL after the termination command or SIGTERM\n  was executed and the running OS child process is still alive.\n* Link Erlang processes to OS processes (via intermediate Erlang Pids that are linked\n  to an associated OS process), so that the termination of an OS process results\n  in termination of an Erlang Pid, and vice versa.\n* Monitor termination of OS processes using `erlang:monitor/2`.\n* Terminate all processes belonging to an OS process group.\n* Kill processes belonging to an OS process group at process exit.\n* Communicate with an OS process via its STDIN.\n* Redirect STDOUT and STDERR of an OS process to a file, erlang process,\n  or a custom function. When redirected to a file, the file can be open in\n  append/truncate mode, and given at creation an access mask.\n* Run interactive processes with psudo-terminal pty support.\n* Support all RFC4254 pty psudo-terminal options defined in\n  [section-8](https://datatracker.ietf.org/doc/html/rfc4254#section-8) of the spec.\n* Execute OS processes under different user credentials (using Linux capabilities).\n* Perform proper cleanup of OS child processes at port program termination time.\n\nThis application provides significantly better control\nover OS processes than built-in `erlang:open_port/2` command with a\n`{spawn, Command}` option, and performs proper OS child process cleanup\nwhen the emulator exits. \n\nThe `erlexec` application has been in production use by Erlang and Elixir systems,\nand is considered stable.\n\n## Donations ##\nIf you find this project useful, please donate to:\n* Bitcoin: `12pt8TcoMWMkF6iY66VJQk95ntdN4pFihg`\n* Ethereum: `0x268295486F258037CF53E504fcC1E67eba014218`\n\n## Supported Platforms\n\nLinux, Solaris, FreeBSD, DragonFly, OpenBSD, MacOS X\n\n## DOCUMENTATION ##\n\nSee https://hexdocs.pm/erlexec/readme.html\n\n## USAGE ##\n\n### Erlang: import as a dependency ###\n\n- Add dependency in `rebar.config`:\n```erlang\n{deps,\n [% ...\n  {erlexec, \"~\u003e 2.0\"}\n  ]}.\n```\n\n- Include in your `*.app.src`:\n```erlang\n{applications,\n   [kernel,\n    stdlib,\n    % ...\n    erlexec\n   ]}\n```\n\n### Elixir: import as a dependency ###\n\n```elixir\ndefp deps do\n  [\n    # ...\n    {:erlexec, \"~\u003e 2.0\"}\n  ]\nend\n```\n\n## BUILDING FROM SOURCE ##\n\nMake sure you have [rebar](http://github.com/basho/rebar) or\n[rebar3](http://github.com/basho/rebar3) installed locally and the rebar script\nis in the path.\n\nIf you are deploying the application on Linux and would like to\ntake advantage of exec-port running tasks using effective user IDs\ndifferent from the real user ID that started exec-port, then\neither make sure that libcap-dev[el] library is installed or make\nsure that the user running the port program has `sudo` rights.\n\nOS-specific libcap-dev installation instructions:\n\n* Fedora, CentOS: \"yum install libcap-devel\"\n* Ubuntu:         \"apt-get install libcap-dev\"\n\n```bash\n$ git clone git@github.com:saleyn/erlexec.git\n$ make\n\n# NOTE: for disabling optimized build of exec-port, do the following instead:\n$ OPTIMIZE=0 make\n```\n\nBy default port program's implementation uses `poll(2)` call for event\ndemultiplexing. If you prefer to use `select(2)`, set the following environment\nvariable:\n```bash\n$ USE_POLL=0 make\n```\n\n## LICENSE ##\n\nThe program is distributed under BSD license.\n\nCopyright (c) 2003 Serge Aleynikov\n\n## Architecture\n\n\u003cpre\u003e\n┌───────────────────────────┐\n│   ┌────┐ ┌────┐ ┌────┐    │\n│   │Pid1│ │Pid2│ │PidN│    │   Erlang light-weight Pids associated\n│   └────┘ └────┘ └────┘    │   one-to-one with managed OsPids\n│         ╲   │   ╱         │\n│          ╲  │  ╱          │\n│           ╲ │ ╱ (links)   │\n│         ┌──────┐          │\n│         │ exec │          │   Exec application running in Erlang VM\n│         └──────┘          │\n│ Erlang VM   │             │\n└─────────────┼─────────────┘\n              │\n        ┌───────────┐\n        │ exec-port │           Port program (separate OS process)\n        └───────────┘\n         ╱    │    ╲\n(optional stdin/stdout/stderr pipes)\n       ╱      │      ╲\n  ┌──────┐ ┌──────┐ ┌──────┐\n  │OsPid1│ │OsPid2│ │OsPidN│    Managed Child OS processes\n  └──────┘ └──────┘ └──────┘\n\u003c/pre\u003e\n\n## Configuration Options\n\nSee description of types in {@link exec:exec_options()}.\n\nThe `exec-port` program requires the `SHELL` variable to be set. If you are\nrunning Erlang inside a docker container, you might need to ensure that `SHELL`\nis properly set prior to starting the emulator.\n\n## Debugging\n\n`exec` supports several debugging options passed to `exec:start/1`:\n\n* `{debug, Level}` - turns on debug verbosity in the port program.\n* `verbose`        - turns on verbosity in the Erlang code.\n* `valgrind`       - runs exec under the Valgrind tool, which needs\n  to be installed in the OS. This generates a local\n  `valgrind.YYYYMMDDhhmmss.log` file containing Valgrind's output.\n  If you need to customize the Valgrind command options, use\n  `{valgrind, \"/path/to/valgrind Args ...\"}` option.\n\n## Examples\n\n### Starting/stopping an OS process\n```erlang\n1\u003e exec:start().                                        % Start the port program.\n{ok,\u003c0.32.0\u003e}\n2\u003e {ok, _, I} = exec:run_link(\"sleep 1000\", []).        % Run a shell command to sleep for 1000s.\n{ok,\u003c0.34.0\u003e,23584}\n3\u003e exec:stop(I).                                        % Kill the shell command.\nok                                                      % Note that this could also be accomplished\n                                                        % by doing exec:stop(pid(0,34,0)).\n```\nIn Elixir:\n```elixir\niex(1)\u003e :exec.start\n{:ok, #PID\u003c0.112.0\u003e}\niex(2)\u003e :exec.run(\"echo ok\", [:sync, :stdout])\n{:ok, [stdout: [\"ok\\n\"]]}\n```\n\n### Clearing environment or unsetting an env variable of the child process\n```erlang\n%% Clear environment with {env, [clear]} option:\n10\u003e f(Bin), {ok, [{stdout, [Bin]}]} = exec:run(\"env\", [sync, stdout, {env, [clear]}]), p(re:split(Bin, \u003c\u003c\"\\n\"\u003e\u003e)).\n[\u003c\u003c\"PWD=/home/...\"\u003e\u003e,\u003c\u003c\"SHLVL=0\"\u003e\u003e, \u003c\u003c\"_=/usr/bin/env\"\u003e\u003e,\u003c\u003c\u003e\u003e]\nok\n%% Clear env and add a \"TEST\" env variable:\n11\u003e f(Bin), {ok, [{stdout, [Bin]}]} = exec:run(\"env\", [sync, stdout, {env, [clear, {\"TEST\", \"xxx\"}]}]), p(re:split(Bin, \u003c\u003c\"\\n\"\u003e\u003e)).\n[\u003c\u003c\"PWD=/home/...\"\u003e\u003e,\u003c\u003c\"SHLVL=0\"\u003e\u003e, \u003c\u003c\"_=/usr/bin/env\"\u003e\u003e,\u003c\u003c\"TEST=xxx\"\u003e\u003e,\u003c\u003c\u003e\u003e]\n%% Unset an \"EMU\" env variable:\n11\u003e f(Bin), {ok, [{stdout, [Bin]}]} = exec:run(\"env\", [sync, stdout, {env, [{\"EMU\", false}]}]), p(re:split(Bin, \u003c\u003c\"\\n\"\u003e\u003e)).\n[...]\nok\n```\n\n### Running exec-port as another effective user\n\nIn order to be able to use this feature the current user must either have `sudo`\nrights or the `exec-port` file must be owned by `root` and have the SUID bit set\n(use: `chown root:root exec-port; chmod 4555 exec-port`):\n\n```bash\n$ ll priv/x86_64-unknown-linux-gnu/exec-port\n-rwsr-xr-x 1 root root 777336 Dec  8 10:02 ./priv/x86_64-unknown-linux-gnu/exec-port\n```\n\nIf the effective user doesn't have rights to access the `exec-port`\nprogram in the real user's directory, then the `exec-port` can be copied to some\nshared location, which will be specified at startup using\n`{portexe, \"/path/to/exec-port\"}`.\n\n```erlang\n$ cp $(find . -name exec-port) /tmp\n$ chmod 755 /tmp/exec-port\n\n$ whoami\nserge\n\n$ erl\n1\u003e exec:start([{user, \"wheel\"}, {portexe, \"/tmp/exec-port\"}]).  % Start the port program as effective user \"wheel\".\n{ok,\u003c0.32.0\u003e}\n\n$ ps haxo user,comm | grep exec-port\nwheel      exec-port\n```\n\n### Allowing exec-port to run commands as other effective users\n\nIn order to be able to use this feature the current user must either have `sudo`\nrights or the `exec-port` file must have the SUID bit set, and the `exec-port` file\nmust have the capabilities set as described in the \"Build\" section above.\n\nThe port program will initially be started as `root`, and then it will \nswitch the effective user to `{user, User}` and set process capabilities to\n`cap_setuid,cap_kill,cap_sys_nice`.  After that it'll allow to run child programs\nunder effective users listed in the `{limit_users, Users}` option.\n\n```erlang\n$ whoami\nserge\n\n$ erl\n1\u003e Opts = [root, {user, \"wheel\"}, {limit_users, [\"alex\",\"guest\"]}],\n2\u003e exec:start(Opts).                                    % Start the port program as effective user \"wheel\"\n                                                        % and allow it to execute commands as \"alex\" or \"guest\".\n{ok,\u003c0.32.0\u003e}\n3\u003e exec:run(\"whoami\", [sync, stdout, {user, \"alex\"}]).  % Command is executed under effective user \"alex\"\n{ok,[{stdout,[\u003c\u003c\"alex\\n\"\u003e\u003e]}]}\n\n$ ps haxo user,comm | grep exec-port\nwheel      exec-port\n```\n\n### Running the port program as root\n\nWhile running the port program as root is highly discouraged, since it opens a security\nhole that gives users an ability to damage the system, for those who might need such an\noption, here is how to get it done (PROCEED AT YOUR OWN RISK!!!).\n\nNote: in this case `exec` would use `sudo exec-port` to run it as `root` or the `exec-port`\nmust have the SUID bit set (4555) and be owned by `root`.  The other (DANGEROUS and\nfirmly DISCOURAGED!!!) alternative is to run `erl` as `root`:\n\n```erlang\n$ whoami\nserge\n\n# Make sure the exec-port can run as root:\n$ sudo _build/default/lib/erlexec/priv/*/exec-port --whoami\nroot\n\n$ erl\n1\u003e exec:start([root, {user, \"root\"}, {limit_users, [\"root\"]}]).\n2\u003e exec:run(\"whoami\", [sync, stdout]).\n{ok, [{stdout, [\u003c\u003c\"root\\n\"\u003e\u003e]}]}\n\n$ ps haxo user,comm | grep exec-port\nroot       exec-port\n```\n\n### Killing an OS process\n\nNote that killing a process can be accomplished by running kill(3) command\nin an external shell, or by executing exec:kill/2.\n```erlang\n1\u003e f(I), {ok, _, I} = exec:run_link(\"sleep 1000\", []).\n{ok,\u003c0.37.0\u003e,2350}\n2\u003e exec:kill(I, 15).\nok\n** exception error: {exit_status,15}                    % Our shell died because we linked to the\n                                                        % killed shell process via exec:run_link/2.\n\n3\u003e exec:status(15).                                     % Examine the exit status.\n{signal,15,false}                                       % The program got SIGTERM signal and produced\n                                                        % no core file.\n```\n\n### Using a custom success return code\n```erlang\n1\u003e exec:start_link([]).\n{ok,\u003c0.35.0\u003e}\n2\u003e exec:run_link(\"sleep 1\", [{success_exit_code, 0}, sync]).\n{ok,[]}\n3\u003e exec:run(\"sleep 1\", [{success_exit_code, 1}, sync]).\n{error,[{exit_status,1}]}                               % Note that the command returns exit code 1\n```\n\n### Redirecting OS process stdout to a file\n```erlang\n7\u003e f(I), {ok, _, I} = exec:run_link(\"for i in 1 2 3; do echo \\\"Test$i\\\"; done\",\n    [{stdout, \"/tmp/output\"}]).\n8\u003e io:format(\"~s\", [binary_to_list(element(2, file:read_file(\"/tmp/output\")))]),\n   file:delete(\"/tmp/output\").\nTest1\nTest2\nTest3\nok\n```\n\n### Redirecting OS process stdout to screen, an Erlang process or a custom function\n```erlang\n9\u003e exec:run(\"echo Test\", [{stdout, print}]).\n{ok,\u003c0.119.0\u003e,29651}\nGot stdout from 29651: \u003c\u003c\"Test\\n\"\u003e\u003e\n\n10\u003e exec:run(\"for i in 1 2 3; do sleep 1; echo \\\"Iter$i\\\"; done\",\n            [{stdout, fun(S,OsPid,D) -\u003e io:format(\"Got ~w from ~w: ~p\\n\", [S,OsPid,D]) end}]).\n{ok,\u003c0.121.0\u003e,29652}\nGot stdout from 29652: \u003c\u003c\"Iter1\\n\"\u003e\u003e\nGot stdout from 29652: \u003c\u003c\"Iter2\\n\"\u003e\u003e\nGot stdout from 29652: \u003c\u003c\"Iter3\\n\"\u003e\u003e\n\n% Note that stdout/stderr options are equivanet to {stdout, self()}, {stderr, self()} \n11\u003e exec:run(\"echo Hello World!; echo ERR!! 1\u003e\u00262\", [stdout, stderr]).\n{ok,\u003c0.244.0\u003e,18382}\n12\u003e flush().\nShell got {stdout,18382,\u003c\u003c\"Hello World!\\n\"\u003e\u003e}\nShell got {stderr,18382,\u003c\u003c\"ERR!!\\n\"\u003e\u003e}\nok\n```\n\n### Appending OS process stdout to a file\n```erlang\n13\u003e exec:run(\"for i in 1 2 3; do echo TEST$i; done\",\n        [{stdout, \"/tmp/out\", [append, {mode, 8#600}]}, sync]),\n    file:read_file(\"/tmp/out\").\n{ok,\u003c\u003c\"TEST1\\nTEST2\\nTEST3\\n\"\u003e\u003e}\n14\u003e exec:run(\"echo Test4; done\", [{stdout, \"/tmp/out\", [append, {mode, 8#600}]}, sync]),\n    file:read_file(\"/tmp/out\").\n{ok,\u003c\u003c\"TEST1\\nTEST2\\nTEST3\\nTest4\\n\"\u003e\u003e}\n15\u003e file:delete(\"/tmp/out\").\n```\n\n### Setting up a monitor for the OS process\n```erlang\n\u003e f(I), f(P), {ok, P, I} = exec:run(\"echo ok\", [{stdout, self()}, monitor]).\n{ok,\u003c0.263.0\u003e,18950}\n16\u003e flush().                                                                  \nShell got {stdout,18950,\u003c\u003c\"ok\\n\"\u003e\u003e}\nShell got {'DOWN',18950,process,\u003c0.263.0\u003e,normal}\nok\n```\n\n### Managing an externally started OS process\nThis command allows to instruct erlexec to begin monitoring given OS process\nand notify Erlang when the process exits. It is also able to send signals to\nthe process and kill it.\n```erlang\n% Start an externally managed OS process and retrieve its OS PID:\n17\u003e spawn(fun() -\u003e os:cmd(\"echo $$ \u003e /tmp/pid; sleep 15\") end).\n\u003c0.330.0\u003e  \n18\u003e f(P), P = list_to_integer(lists:reverse(tl(lists:reverse(binary_to_list(element(2,\nfile:read_file(\"/tmp/pid\"))))))).\n19355\n\n% Manage the process and get notified by a monitor when it exits:\n19\u003e exec:manage(P, [monitor]).\n{ok,\u003c0.334.0\u003e,19355}\n\n% Wait for monitor notification\n20\u003e f(M), receive M -\u003e M end.\n{'DOWN',19355,process,\u003c0.334.0\u003e,{exit_status,10}}\nok\n21\u003e file:delete(\"/tmp/pid\").\nok\n```\n\n### Specifying a custom process shutdown delay in seconds\n```erlang\n% Execute an OS process (script) that blocks SIGTERM with custom kill timeout, and monitor\n22\u003e f(I), {ok, _, I} = exec:run(\"trap '' SIGTERM; sleep 30\", [{kill_timeout, 3}, monitor]).\n{ok,\u003c0.399.0\u003e,26347}\n% Attempt to stop the OS process\n23\u003e exec:stop(I).\nok\n% Wait for its completion\n24\u003e f(M), receive M -\u003e M after 10000 -\u003e timeout end.                                          \n{'DOWN',26347,process,\u003c0.403.0\u003e,normal}\n```\n\n### Specifying a custom kill command for a process\n```erlang\n% Execute an OS process (script) that blocks SIGTERM, and uses a custom kill command,\n% which kills it with a SIGINT. Add a monitor so that we can wait for process exit\n% notification. Note the use of the special environment variable \"CHILD_PID\" by the\n% kill command. This environment variable is set by the port program before invoking\n% the kill command:\n2\u003e f(I), {ok, _, I} = exec:run(\"trap '' SIGTERM; sleep 30\", [{kill, \"kill -n 2 ${CHILD_PID}\"},\n                                                             {kill_timeout, 2}, monitor]).\n{ok,\u003c0.399.0\u003e,26347}\n% Try to kill by SIGTERM. This does nothing, since the process is blocking SIGTERM:\n3\u003e exec:kill(I, sigterm), f(M), receive M -\u003e M after 0 -\u003e timeout end.\ntimeout\n% Attempt to stop the OS process\n4\u003e exec:stop(I).\nok\n% Wait for its completion\n5\u003e f(M), receive M -\u003e M after 1000 -\u003e timeout end.                                          \n{'DOWN',26347,process,\u003c0.403.0\u003e,normal}\n```\n\n### Communicating with an OS process via STDIN\n```erlang\n% Execute an OS process (script) that reads STDIN and echoes it back to Erlang\n25\u003e f(I), {ok, _, I} = exec:run(\"read x; echo \\\"Got: $x\\\"\", [stdin, stdout, monitor]).\n{ok,\u003c0.427.0\u003e,26431}\n% Send the OS process some data via its stdin\n26\u003e exec:send(I, \u003c\u003c\"Test data\\n\"\u003e\u003e).                                                  \nok\n% Get the response written to processes stdout\n27\u003e f(M), receive M -\u003e M after 10000 -\u003e timeout end.\n{stdout,26431,\u003c\u003c\"Got: Test data\\n\"\u003e\u003e}\n% Confirm that the process exited\n28\u003e f(M), receive M -\u003e M after 10000 -\u003e timeout end.\n{'DOWN',26431,process,\u003c0.427.0\u003e,normal}\n```\n\n### Communicating with an OS process via STDIN and sending end-of-file\n\nSometimes a spawned child OS process receiving stdin input needs to detect the\nend of input in order to process incoming data. When piping commands (e.g.\n`cat file | tac`) this is handled by detecting the EOF by the reading end of the\npipe when the writing end of the pipe is closed.  In `erlexec` this is handled\nby explicitly sending the `eof` atom to the OS process that listens to STDIN:\n\n```erlang\n2\u003e Watcher = spawn(fun F() -\u003e receive Msg -\u003e io:format(\"Got: ~p\\n\", [Msg]), F() after 60000 -\u003e ok end end).\n\u003c0.112.0\u003e\n3\u003e f(Pid), f(OsPid), {ok, Pid, OsPid} = exec:run(\"tac\", [stdin, {stdout, Watcher}, {stderr, Watcher}]).\n{ok,\u003c0.114.0\u003e,26143}\n4\u003e exec:send(Pid, \u003c\u003c\"foo\\n\"\u003e\u003e).\nok\n5\u003e exec:send(Pid, \u003c\u003c\"bar\\n\"\u003e\u003e).\nok\n6\u003e exec:send(Pid, \u003c\u003c\"baz\\n\"\u003e\u003e).\nok\n7\u003e exec:send(Pid, eof).   %% \u003c--- sending the EOF command to the STDIN\nok\nGot: {stdout,26143,\u003c\u003c\"baz\\nbar\\nfoo\\n\"\u003e\u003e}\n```\n\n### Running OS commands synchronously\n```erlang\n% Execute an shell script that blocks for 1 second and return its termination code\n29\u003e exec:run(\"sleep 1; echo Test\", [sync]).\n% By default all I/O is redirected to /dev/null, so no output is captured\n{ok,[]}\n\n% 'stdout' option instructs the port program to capture stdout and return it to caller\n30\u003e exec:run(\"sleep 1; echo Test\", [stdout, sync]).\n{ok,[{stdout, [\u003c\u003c\"Test\\n\"\u003e\u003e]}]}\n\n% Execute a non-existing command\n31\u003e exec:run(\"echo1 Test\", [sync, stdout, stderr]).   \n{error,[{exit_status,32512},\n        {stderr,[\u003c\u003c\"/bin/bash: echo1: command not found\\n\"\u003e\u003e]}]}\n\n% Capture stdout/stderr of the executed command\n32\u003e exec:run(\"echo Test; echo Err 1\u003e\u00262\", [sync, stdout, stderr]).    \n{ok,[{stdout,[\u003c\u003c\"Test\\n\"\u003e\u003e]},{stderr,[\u003c\u003c\"Err\\n\"\u003e\u003e]}]}\n\n% Redirect stderr to stdout\n33\u003e exec:run(\"echo Test 1\u003e\u00262\", [{stderr, stdout}, stdout, sync]).\n{ok, [{stdout, [\u003c\u003c\"Test\\n\"\u003e\u003e]}]}\n```\n\n### Running OS commands with/without shell\n```erlang\n% Execute a command by an OS shell interpreter\n34\u003e exec:run(\"echo ok\", [sync, stdout]).\n{ok, [{stdout, [\u003c\u003c\"ok\\n\"\u003e\u003e]}]}\n\n% Execute an executable without a shell (note that in this case\n% the full path to the executable is required):\n35\u003e exec:run([\"/bin/echo\", \"ok\"], [sync, stdout])).\n{ok, [{stdout, [\u003c\u003c\"ok\\n\"\u003e\u003e]}]}\n\n% Execute a shell with custom options\n36\u003e exec:run([\"/bin/bash\", \"-c\", \"echo ok\"], [sync, stdout])).\n{ok, [{stdout, [\u003c\u003c\"ok\\n\"\u003e\u003e]}]}\n```\n\n### Running OS commands with pseudo terminal (pty)\n```erlang\n% Execute a command without a pty\n37\u003e exec:run(\"echo hello\", [sync, stdout]).\n{ok, [{stdout,[\u003c\u003c\"hello\\n\"\u003e\u003e]}]}\n\n% Execute a command with a pty\n38\u003e exec:run(\"echo hello\", [sync, stdout, pty]).\n{ok,[{stdout,[\u003c\u003c\"hello\"\u003e\u003e,\u003c\u003c\"\\r\\n\"\u003e\u003e]}]}\n\n% Execute a command with pty echo\n39\u003e {ok, P0, I0} = exec:run(\"cat\", [stdin, stdout, {stderr, stdout}, pty, pty_echo]).\n{ok,\u003c0.162.0\u003e,17086}\n40\u003e exec:send(I0, \u003c\u003c\"hello\"\u003e\u003e).\nok\n41\u003e flush().\nShell got {stdout,17086,\u003c\u003c\"hello\"\u003e\u003e}\nok\n42\u003e exec:send(I0, \u003c\u003c\"\\n\"\u003e\u003e).\nok\n43\u003e flush().\nShell got {stdout,17086,\u003c\u003c\"\\r\\n\"\u003e\u003e}\nShell got {stdout,17086,\u003c\u003c\"hello\\r\\n\"\u003e\u003e}\nok\n44\u003e exec:send(I, \u003c\u003c3\u003e\u003e).\nok\n45\u003e flush().\nShell got {stdout,17086,\u003c\u003c\"^C\"\u003e\u003e}\nShell got {'DOWN',17086,process,\u003c0.162.0\u003e,{exit_status,2}}\nok\n\n% Execute a command with custom pty options\n46\u003e {ok, P1, I1} = exec:run(\"cat\", [stdin, stdout, {stderr, stdout}, {pty, [{vintr, 2}]}, monitor]).\n{ok,\u003c0.199.0\u003e,16662}\n47\u003e exec:send(I1, \u003c\u003c3\u003e\u003e).\nok\n48\u003e flush().\nok\n49\u003e exec:send(I1, \u003c\u003c2\u003e\u003e).\nok\n50\u003e flush().\nShell got {'DOWN',16662,process,\u003c0.199.0\u003e,{exit_status,2}}\nok\n```\n \n### Kill a process group at process exit\n```erlang\n% In the following scenario the process P0 will create a new process group\n% equal to the OS pid of that process (value = GID). The next two commands\n% are assigned to the same process group GID. As soon as the P0 process exits\n% P1 and P2 will also get terminated by signal 15 (SIGTERM):\n51\u003e {ok, P2, GID} = exec:run(\"sleep 10\",  [{group, 0},   kill_group]).\n{ok,\u003c0.37.0\u003e,25306}\n52\u003e {ok, P3,   _} = exec:run(\"sleep 15\",  [{group, GID}, monitor]).\n{ok,\u003c0.39.0\u003e,25307}\n53\u003e {ok, P4,   _} = exec:run(\"sleep 15\",  [{group, GID}, monitor]).\n{ok,\u003c0.41.0\u003e,25308}\n54\u003e flush().\nShell got {'DOWN',25307,process,\u003c0.39.0\u003e,{exit_status,15}}\nShell got {'DOWN',25308,process,\u003c0.41.0\u003e,{exit_status,15}}\nok\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaleyn%2Ferlexec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaleyn%2Ferlexec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaleyn%2Ferlexec/lists"}