find: How to use found paths in the -exec directive? - bash

I have a dozen files named
~/DOMAIN1.de/bin/dbdeploy.php
~/DOMAIN2.de/bin/dbdeploy.php
~/DOMAIN3.de/bin/dbdeploy.php
I want to run them all with the same arguments.
My bash script reads:
cd ~
find . -maxdepth 1 -type d -name "*\.de" -exec php56 bin/dbdeploy.php "$1" "$2" \;
However, the path given to exec seems not to be relative to the found subdirectory but rather to my PWD:
$ bash -x ./.dbpush "some argument"
+ cd ~
+ find . -maxdepth 1 -type d -name '*\.de' -exec php56 bin/dbdeploy.php 'some argument' ';'
Could not open input file: bin/dbdeploy.php
Could not open input file: bin/dbdeploy.php
Could not open input file: bin/dbdeploy.php
How can I use the found path in the -exec directive?

Ok, actually I found the answer myself:
The "find"-results are stored in {}, so the line reads
find . -maxdepth 1 -type d -name "*\.de" -exec php56 {}/bin/dbdeploy.php "$1" "$2" \;
Alternativly
find . -type f -wholename "*\.de/bin/dbdeploy.php" -exec php56 {} "$1" "$2" \;

Related

Rename file if it is the only one with the extension in directory

This works however I would like to do it only if it is the only .jpg for the given directory, the one below will just rename them all to folder.jpg, overwriting the other files:
find . -type f -name '*.jpg' -execdir mv {} 'folder.jpg' \;
I guess find cannot filter by the number of matches, but you can always exec a shell which does more elaborate checks for you:
find . -type f -name '*.jpg' -execdir sh -c '[ $# = 1 ] && mv "$1" folder.jpg' sh {} +

Using command substitution in find -exec

How can I use command substitution in find … -exec … to avoid using xargs in the following command?
find -L -- /path/to/directory -mindepth 2 -maxdepth 2 -type d -exec dirname '{}' \; | xargs basename -a
I tried the following using command substitution, but it output . for each result instead of the desired output:
find -L -- /path/to/directory -mindepth 2 -maxdepth 2 -type d -exec basename "$(dirname '{}')" \;
Your first command will return strange results if a path contains whitespace.
Use a small shell script:
find -L -- . -mindepth 2 -maxdepth 2 -type d -exec sh -c 'basename "$(dirname "{}")"' \;
Alternative syntax to pass one path argument to the script:
find -L -- . -mindepth 2 -maxdepth 2 -type d -exec sh -c 'basename "$(dirname "$1")"' sh {} \;
Or pass as many arguments to the script as possible:
find -L -- . -mindepth 2 -maxdepth 2 -type d -exec sh -c '
for path do
basename "$(dirname "$path")"
done
' sh {} +
With GNU utilities it's possible to output NUL-terminated strings with dirname passed to xargs -0. The basename command is not run if there are no arguments (-r):
find -L -- . -mindepth 2 -maxdepth 2 -type d -exec dirname -z {} + | xargs -r0 basename -a

Bash - dirname not working with substitution

When using find with $(dirname {}), it always just outputs "." as the dirname. E.g. for:
find . -name \*.h -exec echo {} \; -exec echo $(dirname {}) \;
outputs:
./folder/folder.h
.
./folder/sub_folder/sub_folder.h
.
./test.h
.
But I would expect this:
./folder/folder.h
./folder
./folder/sub_folder/sub_folder.h
./folder/sub_folder
./test.h
.
Interestingly, creating a new shell for each find generates the correct output:
find . -name \*.h -exec sh -c 'echo $0; echo $(dirname $0);' {} \;
After having tested the above command through a script, it has been observed that $(dirname {}) is expanded to current directory .'.
mkdir -p test1/test2
touch test1/test2/tests.h
find . -name \*.h -exec echo {} \; -exec echo $(dirname {}) \;
./test1/test2/tests.h
.
echo "find . -name \*.h -exec echo {} \; -exec echo $(dirname {}) \;" > checkh.sh
bash -vx checkh.sh
find . -name \*.h -exec echo {} \; -exec echo . \;
+ find . -name '*.h' -exec echo '{}' ';' -exec echo . ';'
./test1/test2/tests.h
.
.
That's why the output is always displayed as only . current directory.
So, use your mini-script sh -c style or Kent's solution.
A slight modification from your command will also work, i.e., put echo inside command substitution:
find . -name \*.h -exec echo {} \; -exec $(echo dirname {}) \;
./test1/test2/tests.h
./test1/test2
Test case on the modification is as follows:
echo "find . -name \*.h -exec echo {} \; -exec $(echo dirname {}) \;" > checkh2.sh
bash -vx checkh2.sh
find . -name \*.h -exec echo {} \; -exec dirname {} \;
+ find . -name '*.h' -exec echo '{}' ';' -exec dirname '{}' ';'
./test1/test2/tests.h
./test1/test2
This line gives what you want:
find . -name *.h -print -exec dirname {} \;
You might actually want to use this:
find . -name '*.h' -type f -printf "%p\n%h\n"
When you look at man find under the printf format section, you will notice that there are a plethora of useful flags.

find -exec basename {} vs find -exec echo $(basename {})

I'm sure I'm missing something but I can't figure it out. Given:
$ find -type f
./hello.txt
./wow.txt
./yay.txt
how come the next two commands render different results?
$ find -type f -exec basename {} \;
hello.txt
wow.txt
yay.txt
$ find -type f -exec echo $(basename {}) \;
./hello.txt
./wow.txt
./yay.txt
$(basename {}) is evaluated before the command runs. The result is {} so the command echo $(basename {}) becomes echo {} and basename is not run for each file.
A quick debug on that using the bash -x debugger demonstrated this,
[The example is my own, just for demonstration purposes]
bash -xc 'find -type f -name "*.sh" -exec echo $(basename {}) \;'
++ basename '{}'
+ find -type f -name '*.sh' -exec echo '{}' ';'
./1.sh
./abcd/another_file_1_not_ok.sh
./abcd/another_file_2_not_ok.sh
./abcd/another_file_3_not_ok.sh
And for just basename {}
bash -xc 'find -type f -name "*.sh" -exec basename {} \;'
+ find -type f -name '*.sh' -exec basename '{}' ';'
1.sh
another_file_1_not_ok.sh
another_file_2_not_ok.sh
another_file_3_not_ok.sh
As you can see in the first example, echo $(basename {}) gets resolved in two steps, basename {} is nothing but the basename on the actual file (which outputs the plain file name) which is then interpreted as echo {}. So it is nothing but mimic-ing the exact behaviour when you use find with exec and echo the files as
bash -xc 'find -type f -name "*.sh" -exec echo {} \;'
+ find -type f -name '*.sh' -exec echo '{}' ';'
./1.sh
./abcd/another_file_1_not_ok.sh
./abcd/another_file_2_not_ok.sh
./abcd/another_file_3_not_ok.sh

bash: How to delimit strings to find files

What syntax should I use in a bash script to list files based on 3 dynamic values:
- older than X days
- in a specified directory
- whose name contains a specified string?
FILEAGE=7
FILEDIR='"/home/ecom/tmp"'
FILESTRING='"search-results-*"'
FILES_FOR_REMOVAL=$("/usr/bin/find "${FILEDIR}" -maxdepth 1 -type f -mtime +${FILEAGE} -name "${FILESTRING}" -exec ls -lth {} \;")
echo ${FILES_FOR_REMOVAL}
If I try the above I get:
-bash: /usr/bin/find "/home/ecom/tmp" -maxdepth 1 -type f -mtime +7 -name "search-results-*" -exec ls -lth {} \;: No such file or directory
Remove superfluous quotes:
FILEAGE=7
FILEDIR='/home/ecom/tmp'
FILESTRING='search-results-*'
FILES_FOR_REMOVAL=$(/usr/bin/find "${FILEDIR}" -maxdepth 1 -type f -mtime +${FILEAGE} -name "${FILESTRING}" -exec ls -lth {} \;)
Your syntax for 'find' looks ok. Try removing the quotes around the command string, i.e.
FILES_FOR_REMOVAL=$(/usr/bin/find "${FILEDIR}" -maxdepth 1 -type f -mtime +${FILEAGE} -name "${FILESTRING}" -exec ls -lth {} \;)
FILEAGE=7
FILEDIR='/home/ecom/tmp'
FILESTRING='search-results-*'
/usr/bin/find "${FILEDIR}" -maxdepth 1 -type f -mtime +${FILEAGE} -name "${FILESTRING}" -exec /bin/ls -lth '{}' \;
There were some extra quotes that created the error. Also specify full path to /bin/ls to avoid problems with potential aliasing of ls(1). And to get filenames on a separate line, I dropped the $FILES_FOR_REMOVAL variable. You can also use
/usr/bin/find "${FILEDIR}" -maxdepth 1 -type f -mtime +${FILEAGE} -name "${FILESTRING}" -ls
(I can't add comments, but ... )
To reliably handle file names with spaces, you may want to consider storing the file list in a temp text file instead of a variable and loop through it using a while construct (instead of a for)
For example:
FILEAGE=7
FILEDIR='/home/ecom/tmp'
FILESTRING='search-results-*'
TEMPFILE=".temp${RANDOM}"
CMD="find \"${FILEDIR}\" -maxdepth 1 -type f -mtime +${FILEAGE} -name \"${FILESTRING}\" -exec /bin/ls -lth '{}' \;"
$CMD > $TEMPFILE # write output to file
while read thefile; do
do_somthing_to $thefile
done < $TEMPFILE
rm $TEMPFILE # clean up after
Or, if you're only going to use the list once, pipe the output directly to the while construct:
$CMD | while read thefile; do
do_something_to $thefile
done

Resources