Skip to main content

Your submission was sent successfully! Close

Thank you for signing up for our newsletter!
In these regular emails you will find the latest updates from Canonical and upcoming events where you can meet our team.Close

Thank you for contacting us. A member of our team will be in touch shortly. Close

Multipass `exec` and shells

See also: exec command

How exec parses commands

When you call multipass exec from a shell, the command is first parsed by the shell you are in. The result of that parsing is what the multipass client sees in its argument list (argv).

For example, when multipass exec primary -- ls ~ is entered on a Linux shell, the tilde is translated to the calling user’s local home directory before the command is passed to Multipass. But this is not the case on a Windows PowerShell, because “~” does not have the same meaning there.

Quoting

Quoting also depends on the calling shell. On most Linux and macOS shells, single quotes delimit a string that the shell passes verbatim to the program.

The Windows powershell doesn’t treat single quotes this way. A program called with 'abc def' there would get two arguments: 'abc and def'. Double quotes can be used instead: "abc def", but the string they delimit is subject to shell expansion. For example:

set USER=me
multipass exec -n rich-zorilla -- bash -c "echo %USER%"

The output will be: me.

With Multipass, this is seldom a problem, as expansions use a different syntax on Linux:

multipass exec -n rich-zorilla -- bash -c "echo $USER"

The output in this case is: ubuntu.

How SSH parses commands

Multipass executes the command after -- in the given instance as if there was no further shell in the middle (this is a simplification, as the reality is a little more complicated).

This is slightly different from what one would get with SSH. Consider the following command in a bash shell:

multipass exec mp-builder -- python3 -c 'import sys; print(sys.argv)' foo bar

whose output is: ['-c', 'foo', 'bar'].

When using SSH, the entire command would need to be enclosed within quotes:

ssh -i /var/root/Library/Application\ Support/multipassd/ssh-keys/id_rsa [email protected] python -c 'import sys; print(sys.argv)' foo bar

Sample output:

bash: -c: line 1: syntax error near unexpected token `sys.argv'
bash: -c: line 1: `python -c import sys; print(sys.argv) foo bar'

Using a shell to parse commands

To overcome the above problem with multipass exec, one can still have another shell parse the command in the instance with multipass exec, it just needs to be called explicitly. For example:

multipass exec calm-woodcock -- sh -c 'ls -a ~'

Sample output:

.  ..  .bash_logout  .bashrc  .cache  .profile  .ssh

Similarly, on the Windows Command Prompt:

multipass exec calm-woodcock -- sh -c "ls -a ~"

Provided we use the appropriate quoting for the calling shell, this behaves the same regardless of the host platform. Without sh -c, it also fails on all platforms (although possibly in different ways, depending on whether or not the nested command is quoted). The inner-shell trick provides a more consistent cross-platform experience.

Input/output redirection

The multipass exec command can be used together with piping to redirect input/output between commands run on the host and on the instance. For example, this writes the contents of the current directory on the host to a file called save in the instance rich-zorilla:

ls -la | multipass exec -n rich-zorilla -- bash -c "cat > save"

Conversely, this saves the contents of the home directory inside rich-zorilla to a file on the host:

multipass exec -n rich-zorilla -- bash -c "ls -la" | cat > save

Other shell tricks

Other shell features can be combined with multipass exec for different effects. Here is an example using bash’s here-strings:

multipass exec -n primary -- bash << EOF
> hostname
> whoami
> EOF

Sample output:

primary
ubuntu

And another using command substitution:

ping $(multipass exec rich-zorilla -- hostname -I)

Sample output:

PING 10.239.73.39 (10.239.73.39) 56(84) bytes of data.
64 bytes from 10.239.73.39: icmp_seq=1 ttl=64 time=0.371 ms
64 bytes from 10.239.73.39: icmp_seq=2 ttl=64 time=0.304 ms
64 bytes from 10.239.73.39: icmp_seq=3 ttl=64 time=0.439 ms
^C
--- 10.239.73.39 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2054ms
rtt min/avg/max/mdev = 0.304/0.371/0.439/0.055 ms

Errors or typos? Topics missing? Hard to read? Let us know or open an issue on GitHub.

Last updated 2 months ago. Help improve this document in the forum.