"find -exec print" does not work - shell

> find . -type f -exec print {} \;
find: cannot execute print:: No such file or directory
> find . -type f -exec echo {} \;
f1.txt
f2.txt
...
Why "find -exec print" does not work?
Shell - ksh.

I think print is a shell builtin, not an executable.

Related

From Bash I would like to use find to execute 2 commands on the same file, so I can output the file name and metadata about the file

From bash I need to execute 2 commands on specific files recursively. I am trying to print the filename and meta information at the same time. I need to combine the following 2 commands so first a filename is printed, then the metadata for that file is printed, and then repeat for the next file.
I print the file name with:
find . -wholename '*word/test.wsp -exec echo {} \;
And I print the meta-data with:
find . -wholename ‘*word/test\.wsp’ -exec whisper-info {} \;
However, the second command does not print the filename, so I am unsure which files the meta-data belongs to.
How can I execute the 2 commands simutaneously?
I've tried:
find . -wholename '*word/test.wsp -exec echo {} && whisper-info {} \;
find . -wholename '*word/test.wsp -exec echo && whisper-info {} \;
find . -wholename '*word/test.wsp -exec echo {} && -exec whisper-info {} \;
find . -wholename '*word/test.wsp -exec echo {} \; && whisper-info {} \;
find . -wholename '*word/test.wsp -exec echo {} \; && -exec whisper-info {} \;
And a lot of other combinations.
Just put two -execs.
find . -wholename '*word/test.wsp' -exec echo {} \; -exec whisper-info {} \;
If you have GNU find, perhaps prefer its built-in printf rather than an external echo.
You also consistently seem to have misplaced the closing quote. The string with the wildcard needr to be quoted or escaped to prevent the shell from expanding the wildcard before find runs.
-exec require a single command, but that command can be a shell that executes an arbitrary script consisting of your two commands.
find . -wholename '*word/test.wsp' -exec sh -c 'echo "$1"; whisper-info "$1"' _ {} \;
A few notes:
{} is not embedded in the command. Let the command be a static script that accepts an argument, instead.
_ is a dummy value used to set $0 in the shell.
You can avoid spawning quite so many shells by including a loop in the shell to iterate over multiple arguments provided by find -exec ... +
find . -wholename '*word/test.wsp' -exec sh -c 'for f; do echo "$f"; whisper-info "$f"; done' _ {} +
for f; do ...; done is short for for f in "$#"; do ...; done.

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 and append to files in Unix

I am trying to find files by filename and write to them.
find ./ -name "filename" -type f -exec echo "some string" >> {} \;
This creates a file named {} and writes the string to it for every match instead of writing to the files found.
Try:
... -exec sed '$asome string' -i {} \;
You can spawn a subshell as part of the -exec and then use redirection as usual:
find . -name "filename" -type f -exec bash -c 'echo "some string" >> "$1"' _ {} \;
_ is a dummy handle for $0 within bash -c.

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: execute a command on all files with extension recursively

I'm trying to use the following command:
herbalizer file_name.haml > file_name.erb
Here the file_name.haml is the file name, obviously.
How can I apply this command to all haml files in current directory recursively to all sub-directories? Filename should stay the same as mentioned above, so applying on abc.haml would be herbalizer abc.haml > abc.erb
So far: find . -type f -exec herbalizer {} \;
You're pretty close. You can use basename to strip the extension of the name of each file you find:
find . -type f -name \*.haml -exec sh -c 'herbalizer "{}" > "$(dirname {})/$(basename {} .haml).erb"' \;
I wrapped the i/o redirection in a shell command line. Filenames are enclosed in quotes in case a filename or path component contains spaces.
PS. That got you the job done, but it wasn't very elegant; so here's an alternative that uses bash's built-in substitution:
find . -type f -name \*.haml -exec bash -c 'FN="{}"; herbalizer "{}" > "${FN%.haml}.erb"' \;
You can use basename, dirname and find to get your desired results:
find . -type f -name "*.haml" | while read fname
> do
> herbalizer ${fname} > $(dirname $fname)/$(basename $fname .haml).erb
> done
Another simple method as suggested in the comments sections by #Dummy00001 is:
find . -type f -name "*.haml" | while read fname
> do
> herbalizer ${fname} > ${fname%.haml}.erb
> done
Further reference: Parameter substitution

Resources