When administering remote systems, engineers frequently encounter the need to execute complex commands with sudo privileges through SSH. The fundamental problem emerges when dealing with nested quotes, special characters, and spaces in both the command structure and file paths.
The initial approach of:
ssh -tq myuser@hostname "sudo -u scriptuser bash -c \"ls -al\""
works for simple commands but fails with complex cases like:
[[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory"
The nested quotation requirements create a parsing nightmare where the command interpreter struggles to distinguish between:
- SSH-level quotes
- sudo-level quotes
- bash -c quotes
- Actual command quotes
Method 1: Base64 Encoding
This approach completely bypasses quote escaping issues:
command='[[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory"'
encoded_cmd=$(echo "$command" | base64)
ssh myuser@hostname "echo $encoded_cmd | base64 --decode | sudo -u scriptuser bash"
Method 2: Here Document
Using a here-document provides clean syntax:
ssh -t myuser@hostname <<'EOF'
sudo -u scriptuser bash <<'INNEREOF'
[[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory"
INNEREOF
EOF
Method 3: Argument Array
For precise argument passing:
ssh myuser@hostname sudo -u scriptuser bash -c \
"'[[ -d \"/tmp/Some directory\" ]] && rm -rf \"/tmp/Some directory\"'"
Handling Variables
When environment variables are involved:
ssh myuser@hostname "sudo -u scriptuser bash -c \"
export TARGET=\\\"/tmp/Some directory\\\";
[[ -d \\\"\$TARGET\\\" ]] && rm -rf \\\"\$TARGET\\\"
\""
Multi-line Commands
For complex scripts:
ssh myuser@hostname "sudo -u scriptuser bash -s" <<'EOF'
if [[ -d "/tmp/Some directory" ]]; then
rm -rf "/tmp/Some directory"
mkdir -p "/tmp/New directory"
fi
EOF
- Always test commands locally first using
echo
- Consider using
set -x
for debugging - For production systems, prefer configuration management tools
- Document complex command chains in README files
When executing commands remotely via SSH that require both user switching (sudo -u) and complex argument handling, we quickly encounter multiple layers of escaping hell. The fundamental challenge stems from:
1. SSH's own command parsing layer
2. The sudo command's argument processing
3. The bash -c interpretation level
4. The actual command's quoting requirements
For complex multi-step operations, using a here-document avoids most quoting issues:
ssh -t myuser@hostname <<'EOF'
sudo -u scriptuser bash -c '
if [[ -d "/tmp/Some directory" ]]; then
rm -rf "/tmp/Some directory"
fi
'
EOF
Key advantages:
- Single quoting level for the entire command block
- Preserves original command formatting
- Allows using both single and double quotes freely
For maximum reliability with arbitrary complex commands:
cmd='[[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory"'
encoded_cmd=$(echo "$cmd" | base64 -w0)
ssh myuser@hostname "sudo -u scriptuser bash -c \"\$(echo $encoded_cmd | base64 -d)\""
This approach:
- Completely bypasses all quoting issues
- Handles special characters and whitespace perfectly
- Works for multi-line scripts
For production environments, consider:
ssh myuser@hostname "cat > /tmp/remote_script.sh <<'SCRIPT_EOF'
#!/bin/bash
if [[ -d \"/tmp/Some directory\" ]]; then
rm -rf \"/tmp/Some directory\"
fi
SCRIPT_EOF
sudo -u scriptuser bash /tmp/remote_script.sh
rm /tmp/remote_script.sh"
- Always test commands locally first with printf to visualize escaping
- Use -t flag with ssh when sudo requires a terminal
- Consider SSH ControlMaster for multiple command executions
- For repeated operations, set up passwordless sudo for specific commands
When things go wrong, use these debugging approaches:
# View how the remote shell receives the command
ssh myuser@hostname "echo 'sudo command received:'; printf '%q\\n' \"sudo -u scriptuser bash -c \\\"your command\\\"\""
# Test quoting with simple commands first
ssh myuser@hostname "sudo -u scriptuser bash -c 'echo \"Test spaces in: /tmp/Some directory\"'"