As other answers have already identified, ${f%.*}
is expanded by the shell before it runs the xargs
command. You need this expansion to happen once for each file name, with the shell variable f
set to the file name (passing -I f
doesn't do that: xargs
has no notion of shell variable, it looks for the string f
in the command, so if you'd used e.g. xargs -I e echo …
it would have executed commands like ./somedir/somefile.wmacho .mp3
).
Keeping on this approach, tell xargs
to invoke a shell that can perform the expansion. Better, tell find
— xargs
is a largely obsolete tool and is hard to use correctly, as modern versions of find have a construct that does the same job (and more) with fewer plumbing difficulties. Instead of find … -print0 | xargs -0 command …
, run find … -exec command … {} +
.
find . -name '*.wma' -type f -exec sh -c 'for f; do echo "${f%.*}.mp3"; done' _ {} +
The argument _
is $0
in the shell; the file names are passed as the positional arguments which for f; do …
loops over. A simpler version of this command executes a separate shell for each file, which is equivalent but slightly slower:
find . -name '*.wma' -type f -exec sh -c 'echo "${0%.*}.mp3"' {} \;
You don't actually need to use find
here, assuming you're running a reasonably recent shell (ksh93, bash ≥4.0, or zsh). In bash, put shopt -s globstar
in your .bashrc
to activate the **/
glob pattern to recurse in subdirectories (in ksh, that's set -o globstar
). Then you can run
for f in **/*.wma; do
echo "${f%.*}.mp3"
done
(If you have directories called *.wma
, add [ -f "$f" ] || continue
at the beginning of the loop.)
Short answer:
If you just want to pass a bunch of lines as arguments to another command, xargs
is your friend - in this case, because you're putting it into the middle of a command, you'll want to use the -I {}
flag. This sets {}
as a placeholder so that you can put wherever (you can set your own placeholder, but I usually stick to {}
since it won't be mistaken for many other things.
Putting it all together (assuming the commands you've given do what you think they do!):
find server/lib -type f -exec basename {} \; | cut -f 1 -d '.' | xargs -I {} grep -R --exclude-dir=node_modules {} . -l
Long answer
Ok, so we can probably take this further. Let's say you wanted the format you mentioned earlier - you can xargs
to sh -c
so you can chain commands:
find server/lib -type f -exec basename {} \; | cut -f 1 -d '.' | xargs -I {} sh -c "echo {}; grep -R --exclude-dir=node_modules {} . -l"
Running this on my machine, it looks like this is giving you the files where the file is found. My brain is tired, so instead of wrapping it in a nice script (which is what you should actually do when oneliners start to get gnarly, and use https://www.shellcheck.net/ while you're at it) you can have this horrible hack instead, which (I think) will give you the output you're looking for:
find server/lib -type f -exec basename {} \; | cut -f 1 -d '.' | xargs -I {} sh -c "echo {}; grep -R --exclude-dir=node_modules {} . -l && printf \"\n\" || printf \"Not found\n\n\""
Best Answer
One way is to expand the list of files and give it to rm as arguments:
**That will fail with file names that have spaces or new lines.
You may use xargs to build the rm command, like this:
** Will also fail on newlines or spaces
Or better, ask
find
to execute the command rm:But the best solution is to use the delete option directly in find: