Passing bash’s $@ over SSH while preserving spaces requires using an array instead of a string. By constructing a local array with the arguments and passing it to ssh, you avoid shell expansion issues. This method ensures that each argument is transmitted exactly as intended, regardless of internal whitespace or special characters.
The Problem with String Expansion
When you attempt to pass command-line arguments directly to ssh, the most common mistake is treating the arguments as a single string. Consider the following naive approach:
cmd="journalctl -u my-service"
ssh user@host "$cmd"
This works for simple commands without spaces in arguments. However, if your arguments contain spaces, such as journalctl --since "yesterday", the remote shell will split the string incorrectly. The remote side sees journalctl, --since, and yesterday" as separate tokens, leading to errors because the quotes are consumed by the local shell before transmission.
Using $@ directly inside a double-quoted string like "$@" also fails because it flattens the array into a single space-separated string. The remote shell cannot distinguish where one argument ends and the next begins if they were originally separated by spaces within a single argument.
The Array Solution
The robust solution is to use Bash arrays. Arrays preserve the boundary between arguments. You can construct an array locally and pass it to ssh. The key is to pass the array elements individually, not as a joined string.
# Define the command and arguments in an array
args=(journalctl -u my-service --since "yesterday")
# Pass the array to ssh
ssh user@host "${args[@]}"
Here, ${args[@]} expands to each element of the array as a separate word. The remote shell receives the arguments correctly: journalctl, -u, my-service, --since, and yesterday as distinct entities. This method handles spaces, tabs, and other whitespace characters within individual arguments perfectly.
Why Not Use %q?
You might encounter suggestions to use Bash’s %q format specifier for escaping. While %q is useful for generating safe shell code strings, it is not necessary when passing arrays over SSH. Using %q adds complexity and can sometimes lead to double-escaping issues. The array method is cleaner, more readable, and less error-prone.
For more details on managing SSH sessions effectively, see How to Create a Shell Script to Exit SSH Session After Executing Remote Commands.
Handling Special Characters
If your arguments contain characters that are dangerous in shell contexts, such as semicolons or pipes, the array method still protects you. Since the arguments are passed as separate words, the remote shell treats them as literal data unless explicitly interpreted. This prevents injection attacks where a malicious argument could execute unintended commands.
However, be cautious with how you construct the array. If you build the array dynamically from user input, ensure you do not concatenate strings in a way that merges arguments. Always add elements individually:
args=("journalctl")
args+=("-u")
args+=("$service_name")
args+=("--since")
args+=("$time_filter")
This approach ensures that even if $service_name contains spaces, it remains a single argument.
Alternative: Using a Wrapper Script
In some cases, you may prefer to send a script rather than individual arguments. You can write a small wrapper script on the remote host that accepts arguments via standard input or environment variables. This is particularly useful for complex commands with many options.
Another alternative is to use scp to transfer a script file and then execute it remotely. This avoids the complexities of argument passing entirely but introduces overhead for file transfer. For faster execution, consider optimizing your SSH connection settings as discussed in Why can I ssh and return a result faster than Linux can with a bundled command?.
Debugging Transmission Issues
If you encounter unexpected behavior, enable verbose mode on the SSH client to inspect the connection setup. Use ssh -v to see detailed logs. This helps identify if the issue lies in the SSH configuration rather than the argument passing.
Additionally, check the remote system’s journal or logs for errors related to the executed command. Sometimes, the command runs successfully but produces output that is difficult to parse due to encoding issues. Ensure your terminal supports UTF-8 if dealing with international characters.
Common Pitfalls
- Quoting Errors: Avoid wrapping the entire array in quotes when passing to SSH. Only quote individual elements during construction if they contain spaces.
- Empty Arguments: Be aware that empty strings in an array are still passed as arguments. If this causes issues, filter them out before transmission.
- Environment Variables: Environment variables are inherited by default. If you need to restrict the environment, use
ssh -Tto disable pseudo-terminal allocation, which can help prevent certain variable leaks.
Frequently Asked Questions
Can I use $* instead of $@ in the array expansion?
No, you must use $@. Unlike $*, which joins all arguments into a single string, $@ preserves each argument as a separate entity. Using $* would defeat the purpose of using an array for safe argument passing.
Does this method work with sudo on the remote host?
Yes, you can include sudo as the first element of the array. For example, args=(sudo journalctl ...). The remote shell will execute sudo with the subsequent arguments correctly. Ensure the user has appropriate permissions to run sudo without a password prompt if automating.
What happens if an argument contains a newline character?
Newline characters within an argument are preserved because the array method passes arguments as binary-safe chunks. The remote shell will treat the newline as part of the argument string, not as a separator. This is safer than string-based approaches where newlines might be misinterpreted.
Is there a performance penalty for using arrays?
The performance impact is negligible. The overhead of constructing the array and expanding it is minimal compared to the network latency of the SSH connection itself. This method is efficient and recommended for reliability.
How do I handle arguments with single quotes?
Bash arrays handle single quotes naturally. When constructing the array, use double quotes around the variable assignment if the value contains spaces. Single quotes inside the value do not need special escaping. For example, args=("echo 'hello world'") will pass echo and 'hello world' as two arguments.