Reputation: 21503
I'm trying to make a function that basically takes in arguments like f hello there my friend
and searches a directory using find
for all occurences of any of those strings, so it would be find | grep 'hello\|there\|my\|friend'
. I'm new to shell scripting, but my code is below:
function f {
cmd="find | grep '"
for var in "$@"
do
cmd="$cmd$var\\|"
done
cmd="${cmd%\\|}'"
echo "$cmd"
$cmd
}
When I execute the command, I get this:
# f hello there my friend
find | grep 'hello\|there\|my\|friend'
find: `|': No such file or directory
find: `grep': No such file or directory
find: `\'hello\\|there\\|my\\|friend\'': No such file or directory
Why does it not work, and how can I make it work? I imagine it's something to do with the string not being converted to a command, but I don't know enough about how shell scripting works to figure it out.
Upvotes: 17
Views: 14962
Reputation: 3294
I got here because it was the only google SO hit for when I wanted to build a commandline and my parameters included spaces.
The solution was to use a bash array:
#! /bin/bash
# using /bin/sh here might work if sh is broken or mapped to bash but this is not compatible with a real sh.
MY_ARGS=( "--HI" "--there" "--With spaces" )
if [ "X$OPTIONAL" != "X" ] ; then MY_ARGS+=( "$OPTIONAL" );fi
MY_ARGS+=( "$@" )
my_command "${MY_ARGS[@]}"
Adapted from: http://mywiki.wooledge.org/BashFAQ/050
Upvotes: 4
Reputation: 42753
It looks like your command syntax is correct. To run the command from a script in bash and capture the result, use this syntax:
cmd_string="ls"
result=$($cmd_string)
echo $result
Upvotes: 11
Reputation: 11
The most universal way: you can use symbols like "|" (pipe) and other variables into the input and get the result from stdout.
function runcmd(){
eval "$1"
}
result=$(runcmd "Any command ${here}")
Upvotes: 1
Reputation: 46903
Instead of piping the output of find
through grep
, you might as well use the full capabilities of find
. You'll want to build up a an array that contains the options:
-false -o -name '*string1*' ... -o -name '*stringn*'
to pass to find
(where string1
... stringn
are the strings passed as arguments):
f() {
local i args=( -false )
for i; do
args+=( -o -name "*$i*" )
done
find "${args[@]}"
}
We're using -false
as an initializer, so that building up the array of options is simple; this also has the benefit (or flaw, depending on your point of view) that if no options are given then find
exits early without listing all the content of the directory recursively.
With grep
you could use regexes to have more powerful matching capabilities; here we're using find
's -name
option, so we can only use the basic globs: *
, ?
and [...]
. If your find
supports the -regex
option (GNU find
does), and if you really need regexes, then it's trivial to modify the previous function.
Another possibility is to use Bash's extended globs:
f() (
IFS='|' eval 'glob="$*"'
shopt -s globstar extglob nullglob
IFS=
printf '%s\n' **/*@($glob)*
)
A few things to note here:
eval
but in a safe way: it's actually an idiomatic way to join the elements of the positional parameters with the first character of IFS
(here a pipe character).IFS
to the empty string so as to avoid word splitting in the glob **/*@($glob)*
.**/*@($glob)*
uses globstar
and the extglob
@($glob)
(with no quotes, it's not a typo). See Pattern Matching in the reference manual.This function uses Bash's extended globs, that differ from (and aren't as powerful as) regexes (yet this should be enough for most cases).
Upvotes: 4
Reputation: 532313
Don't put the entire command in a string; just build the argument for grep
f () {
local grep_arg=''
delim=''
for var in "$@"; do
grep_arg+="$delim$var"
delim='\|'
done
echo "find | grep '$grep_arg'"
find | grep "$grep_arg"
}
Upvotes: 3