Reputation: 27225
Recently, I switched to zsh because of its many features. I'm curious: Is there a feature which expands wildcards such that the command is executed once for each match instead of only one time for all matches at once.
The command ebook-convert input_file output_file [options]
accepts just one input file. When I want to convert multiple files, I have to execute the command multiple times manually or use a loop, for instance:
for i in *.epub; do
ebook-convert "$i" .mobi
done
What I'd like is a wildcard that functions like the loop so that I can save a few keystrokes. Let said wildcard be ⁂
. The command
ebook-convert ⁂.epub .mobi
should expand to
ebook-convert 1stMatch.epub .mobi
ebook-convert 2ndMatch.epub .mobi
ebook-convert 3rdMatch.epub .mobi
...
I accepted an answer that works for me (thanks to Grisha Levit). But if you know other shells with such a feature, alternative commands which are shorter than writing a loop, or even a way to extend zsh with the wanted wildcard your answers are appreciated.
Upvotes: 7
Views: 3420
Reputation: 8617
so that I can save a few keystrokes
OK, so let's say you typed out
ebook-convert *.epub .mobi
…and now you realized that this isn't going to work — you need to write a loop. What would you normally do? Probably something like:
; done
to the end of the linefor i in
…Let's write this out the steps in terms of readline commands and regular keypresses:
end-of-line # (start from the end for consistency) ; done # type in the loop closing statement character-search-backward * # go back to the where the glob is shell-backward-word # (in case the glob is in the mid-word) shell-kill-word # "cut" the word with the glob "$i" # type the loop variable beginning-of-line # go back to the start of the line for i in # type the beginning of the loop opening yank # "paste" the word with the glob ; do # type the end of the loop opening
For any readline command used above that does not have a key-binding, we need to create one. We also need to create a binding for the new macro that we are creating.
Unless you've already done a lot of readline customization, running the commands below will set the bindings up for the current shell. This uses default bindings like \C-e
➙ end-of-line
.
bind '"\eB": shell-backward-word'
bind '"\eD": shell-kill-word'
bind '"\C-i": "\C-e; done\e\C-]*\eB\eD \"$i\"\C-afor i in\C-y; do "'
The bindings can also go into the inputrc
file for persistence.
After setting things up:
Type in something like
ebook-convert *.epub .mobi
The line will transform into
for i in *.epub; do ebook-convert "$i" .mobi; done
If you want to run the command right away, you can modify the macro to append a \C-j
as the last keypress, which will trigger accept-line
(same as hitting Return).
Upvotes: 4
Reputation: 531315
The for
loop has a shortened form that you might like:
for f (*.epub) ebook-convert $f .mobi
Upvotes: 2
Reputation: 6995
You could make yourself a script that does this :
#!/bin/bash
command="$1"
shift
if
[[ $# -lt 3 ]]
then
echo "Usage: command file/blog arg1, arg2..."
exit 1
fi
declare -a files=()
while [ "$1" != "--" ]
do
[ "$1" ] || continue
files+=("$1")
shift
done
if
[ "$1" != "--" ]
then
echo "Separator not found : end file list with --"
exit 1
fi
shift
for file in "${files[@]}"
do
"$command" "$file" "$@"
done
You cal call this like this (assumes the script is called apply_to
).
apply_to command /dir/* arg1, arg2...
EDIT
I modified the code to insert filenames at the beginning of the command.
Upvotes: 0
Reputation: 5319
You could checkout zargs
in zsh.
This function has a similar purpose to GNU xargs. Instead of reading lines of arguments from the standard input, it takes them from the command line
So, we could write:
autoload -Uz zargs
zargs -I⁂ -- *.epub -- ebook-convert ⁂ .mobi
PS: you could find zmv
is handy if you need to capture some portions of patterns for building commands.
Upvotes: 5