I have a bash script which needs to run several xdotool
commands in a sequence. I cannot get the type
commands with strings to run properly.
It works fine if I type them out manually:
#!/bin/bash
xdotool type 'git status'
xdotool key KP_Enter
But I now need to do some other bits such as sleeping in-between the commands, so it is more sustainable to move to an array of commands and a for loop:
#!/bin/bash
declare -a COMMANDS=(
"type 'git status'"
"key KP_Enter"
)
for COMMAND in "${COMMANDS[@]}"; do
xdotool "$COMMAND"
#Do some other stuff...
done
I have tried every combination of single quotes, double quotes and back ticks I can think of but it always either eats the space in 'git status'
:
$ 'gitstatus'
Command 'gitstatus' not found, did you mean:
command 'mgitstatus' from deb mgitstatus (2.2+dfsg-2)
Try: sudo apt install <deb name>
Or xdotool doesn’t register the commands:
xdotool: Unknown command: type 'git status'
Run 'xdotool help' if you want a command list
xdotool: Unknown command: key KP_Enter
Run 'xdotool help' if you want a command list
2
Answers
Understanding The Problem
When you expand a scalar variable in a quoted context, the exact contents of that variable become part of exactly one string. That means that quotes inside the variable are just data, instead of acting as shell syntax — so using JSON for clarity, you’re effectively running
["xdotool", "type 'git status'"]
, when you instead want to run["xdotool", "type", "git status"]
.Leaving out the syntactic double quotes isn’t better: that makes your code run
["xdotool", "type", "'git", "status'"]
, splitting on spaces but not treating the quotes as special but just passing them as data within the individual arguments.To make sure the quotes are treated as syntax and tell the shell which words to coalesce together, you need to do something different.
Approach A: One Array Per Command
This doesn’t require any non-bash tool, but it does require your data structure to be modified — notice how here we’re defining a separate array for each command to run, and then iterating over variables with the prefix all those arrays share.
Approach B: Interpret String To Array Before Running
I’m using xargs here; we have existing Q&A that provides alternatives, including Python. In pretty much every case (that isn’t abjectly insecure as with
eval
), however, this requires calling an external tool rather than using only bash builtins.Insecure Approach C: Using
eval
Mind, if you trust all your commands not to do anything malicious if parsed as shell syntax, there’s an easier answer here:
eval
combines all its arguments into one string (which we’re mooting for clarity by making everything one string explicitly), then parses that string as syntax (so quotes that would otherwise be literal data instead have their syntactic meaning).Do not ever do this when your string has had untrusted data (filenames, command-line arguments, or anything else you-as-the-developer haven’t personally vetted to be safe) substituted in.
Approach D
As long as the quoting/escaping is compatible, you can use
xargs
directly:note: the
-L1
tellsxargs
to runxdotool
with the arguments provided by one (non-empty) line of input