Bash How reuse found files name? - bash

I want to run a script in a directory in order to generate an svg file for each found dot file. Something like:
find . -name "*.dot" -exec dot -Tsvg \{} \;
This works fine but just output the result on stdout. usually I am using a redirection to generate the svg file. How can I get the dot file name to use it in the redirection like
find . -name "*.dot" -exec dot -Tsvg > "$dotfilename".svg \{} \;

The following:
for i in `find . -name "*.dot"`; do
dot -Tsvg $i > $i.svg
done
performs the find (in backticks) and loops over the results, executing dot for each one. The filename is in $i.
This uses backtick substitution and is a useful mechanism for capturing command output for subsequent use in another command.
To remove the extension and add another, use ${i%.*}.svg. See this SO answer for more info.

You don't need output redirection. Use -O to save to a file whose name is automatically created from the input file name and the output format.
find . -name "*.dot" -exec dot -Tsvg -O \{} \;
Just to point out that you can use {} multiple times in the argument to -exec:
find . -name "*.dot" -exec dot -Tsvg -o \{}.svg \{} \;
Where the first would produce "foo.svg" from "foo.dot", the second would produce "foo.dot.svg"

You can't do the redirection here. find does execute using execvp(3) or friends, not the shell. Instead, use shell globs, or make a script which you then can call from find
An example:
for i in ./*.dot
do
svg=${i%.dot}.svg
dot -Tsvg "$i" > "$svg"
done

The problem that you are having is that redirection is processed before the find command. You can work around this by spawning another bash process in the -exec call. This also makes it easy to remove the extension using parameter expansion.
find . -name "*.dot" -exec bash -c 'dot -Tsvg "$1" > "${1%.*}".svg' -- {} \;

Related

Using fish shell builtins with find exec

I'm trying to source a file that I can get from the output of find using these commands:
find ./ -iname activate.fish -exec source {} \;
and
find ./ -iname activate.fish -exec builtin source {} \;
But both these commands give the error of the form find: ‘source’: No such file or directory or find: ‘builtin’: No such file or directory. Seems like exec of find is not able to recognize fish's builtins ?
What I basically want to achieve is a single command that will search for Python's virtualenv activate scripts in the current directory and execute them.
So doing something like -exec fish -c 'source {}; \ would not help. I've tried it as well and it doesn't error out but does not make the changes either.
Any ideas what can be done for this ?
Thanks!
Perhaps you need:
for file in (find ./ -iname activate.fish)
source $file
end
# or
find ./ -iname activate.fish | while read file
source $file
end
Command substitution executes the command, splits on newlines, and returns that list.
As mentioned in comments, seems like -exec does not run in or affect the current shell environment. So find -exec is not gonna work for my use case.
Instead, this will work:
source (find ./ -iname activate.fish)

How to use find and prename to reformat directory names recursively?

I am trying to find all directories that start with a year in brackets, such as this:
[1990] Nature Documentary
and then rename them removing brackets and inserting a dash in between.
1990 - Nature Documentary
The find command below seems to find the results, however I could not prefix the pattern with ^ to mark start of directory name otherwise its not returning hits.
I am pretty sure I need to use -exec or -execdir, but I am not sure how to store the found pattern and manipulate it.
find . -type d -name '\[[[:digit:]][[:digit:]][[:digit:]][[:digit:]]] *'
With [p]rename:
-depth -exec prename -n 's/\[(\d{4})]([^\/]+)$/$1 -$2/' {} +
Drop -n if the output looks good.
Without it, you'd need a shell script with several hardly intelligible parameter expansions there:
-depth -exec sh -c '
for dp; do
yr=${dp##*/[} yr=${yr%%]*}
echo mv "$dp" "${dp%/*}/$yr -${dp##*/\[????]}"
done' sh {} +
Remove echo to apply changes.
You can use the rename command
find . -type d -name '\[[[:digit:]][[:digit:]][[:digit:]][[:digit:]]\] *'| rename -n 's/(\[\d{4}\]) ([\w,\s]+)+$/$1 - $2/'
Note: The effect will not take place until you delete the -n option.

How do I recursively find files with specific names and join using ImageMagick in Terminal?

I have created an ImageMagick command to join images with certain names:
convert -append *A_SLIDER.jpg *B_SLIDER.jpg out.jpg
I have lots of folders with files named *A_SLIDER.jpg and *B_SLIDER.jpg next to each other (only ever one pair in a directory).
I would like to recursively search a directory with many folders and execute the command to join the images.
If it is possible to name the output image based on the input images that would be great e.g.
=> DOGS_A_SLIDER.jpg and DOGS_B_SLIDER.jpg would combine to DOGS_SLIDER.jpg
Something like this, but back up first and try on a sample directory only!
#!/bin/bash
find . -name "*A_SLIDER*" -execdir bash -c ' \
out=$(ls *A_SLIDER*);
out=${out/_A/}; \
convert -append "*A_SLIDER*" "*B_SLIDER*" $out' \;
Find all files containing the letters "A_SLIDER" and go to the containing directory and start bash there. While you are there, get the name of the file, and remove the _A part to form the output filename. Then execute ImageMagick convert with the _A_ and the corresponding _B_ files to form the output file.
Or, a slightly more concise suggestion from #gniourf_gniourf... thank you.
#!/bin/bash
find . -name "*A_SLIDER.jpg" -type f -execdir bash -c 'convert -append "$1" "${1/_A_/_B_}" "${1/_A/}"' _ {} \;
The "find" command will recursively search folders:
$ find . -name "*.jpg" -print
That will display all the filenames. You might instead want "-iname" which does case-insensitive filename matching.
You can add a command line with "-exec", in which "{}" is replaced by the name of the file. You must terminate the command line with "\;":
$ find . -name "*.jpg" -exec ls -l {} \;
You can use sed to edit the name of a file:
$ echo DOGS_A_SLIDER.jpg | sed 's=_.*$=='
DOGS
Can you count on all of your "B" files being named the same as the corresponding "A" files? That is, you will not have "DOGS_A_SLIDER.jpg" and "CATS_A_SLIDER.jpg" in the same directory. If so, something like the following isn't everything you need, but will contribute to your solution:
$ find . -type f -name "*.jpg" -exec "(echo {} | sed 's=_.*==')" \;
That particular sed script will do the wrong thing if you have any directory names with underscores in them.
"find . -type f" finds regular files; it runs modestly faster than without the -type. Use "-d" to find directories.

help using xargs to pass mulitiple filenames to shell script

Can someone show me to use xargs properly? Or if not xargs, what unix command should I use?
I basically want to input more than (1) file name for input <localfile>, third input parameter.
For example:
1. use `find` to get list of files
2. use each filename as input to shell script
Usage of shell script:
test.sh <localdir> <localfile> <projectname>
My attempt, but not working:
find /share1/test -name '*.dat' | xargs ./test.sh /staging/data/project/ '{}' projectZ \;
Edit:
After some input from everybody and trying -exec, I am finding that my <localfile> filename input with find is also giving me the full path. /path/filename.dat instead of filename.dat. Is there a way to get the basename from find? I think this will have to be a separate question.
I'd just use find -exec here:
% find /share1/test -name '*.dat' -exec ./test.sh /staging/data/project/ {} projectZ \;
This will invoke ./test.sh with your three arguments once for each .dat file under /share1/test.
xargs would pack up all of these filenames and pass them into one invocation of ./test.sh, which doesn't look like your desired behaviour.
If you want to execute the shell script for each file (as opposed to execute in only once on the whole list of files), you may want to use find -exec:
find /share1/test -name '*.dat' -exec ./test.sh /staging/data/project/ '{}' projectZ \;
Remember:
find -exec is for when you want to run a command on one file, for each file.
xargs instead runs a command only once, using all the files as arguments.
xargs stuffs as many files as it can onto the end of the command line.
Do you want to execute the script on one file at a time or all files? For one at a time, use file's exec, which it looks like you're already using the syntax for, and which xargs doesn't use:
find /share1/test -name '*.dat' -exec ./test.sh /staging/data/project/ '{}' projectZ \;
xargs does not have to combine arguments, it's just the default behavior. this properly uses xargs, to execute the commands, as intended.
find /share1/test -name '*.dat' -print0 | xargs -0 -I'{}' ./test.sh /staging/data/project/ '{}' projectZ
When piping find to xargs, NULL termination is usually preferred, I recommend appending the -print0 option to find. After which you must add -0 to xargs, so it will expect NULL terminated arguments. This ensures proper handling of filenames. It's not POSIX proper, but considered well supported. You can always drop the NULL terminating options, if your commands lack support.
Remeber while find's purpose is finding files, xargs is much more generic. I often use xargs to process non-filename arguments.

How does command substitution work with find?

I have the following command
find . -name "*.tiff" -exec echo `basename -s .tiff {}` \;
I expect this to print all my .tiff-files without their file extensions. What I get is
./file1.tiff
./file2.tiff
...
The command,
find . -name "*.tiff" -exec basename -s .tiff {} \;
does yield
file1
file2
...
Is this not supposed to be the input of echo?
The content of the backticks is executed before the find command - yielding just the placeholder {}, which is used in the find command line - hence your result. You can always use set -x to examine what the shell is up to.
Use single-quote characters (') instead of backticks (`) - putting a command in backticks causes it to be executed and replaced by its output in your command.
Also, modify the command to get rid of the echo, like this:
find . -name "*.tiff" -exec 'basename -s .tiff {}' \;
This will execute basename on each found file.

Resources