I am writing a (one-line) script which should recurse thru subdirectories. Find .txt files containing hyperlinks. Use wget to get the contents and download it in the same directory where the text file is located.
Assume all text files found only contain valid hyperlinks.
To test this:
Create a subdirectory ./s1
Create a text file ./s1/s1.txt
Contents of ./s1/s1.txt
: www.google.com
This is the one-liner:
find . -type f -name "*.txt" -exec bash -cx "wget -i \"{}\" -P $(dirname \"{}\") " \;
The problem is that $(dirname \"{}\")
does not expand correctly. The bash command being excuted is:
+ wget -i ./s1/s1.txt -P .
So $(dirname \"{}\")
returns .
The effect is that a new directory ./s1/s1.txt
is created. So the downloaded file is stored as ./s1/s1.txt/index.html
When I replace $(dirname \"{}\")
with $(echo \"{}\")
the output becomes:
+ wget -i ./s1/s1.txt -P ./s1/s1.txt
So parameter passing as such is correct. So I assume the result dirname
is not properly returned to the calling bash shell. Or dirname
is not evaluated at all.
When I execute only the bash command
bash -cx "wget -i ./s1/s1.txt -P $(dirname ./s1/s1.txt)"
(so outside the find
command) the command is executed as expected:
+ wget -i ./s1/s1.txt -P ./s1
What is the correct way to make this one-liner work?
$(dirname \"{}\")
is being expanded by the interactive shell, rather than passed to thefind
command. In general, don't try to embed the find placeholder{}
in shell code - pass it as a positional parameter instead. See for example Is it possible to usefind -exec sh -c
safely?$(dirname \"{}\")
within double quotes, so the shell expands it immediately, runningdirname
with the single argument"{}"
(literal quotes included). There's no directory given in that path, so it returns.
.find
to expand the command substitution, you have to prevent the outer shell from doing it before runningfind
. See What is the difference between the "...", '...', $'...', and $"..." quotes in the shell?