Is it properly quoted when I using '*' or '**/*' in bash? - bash

find -name '*.jpg' -print0 | xargs -0 qiv
qiv **/*.jpg
both are safely escaped and delivered to qiv?

Yes. In the first case, find is expanding the wildcard internally, and delivering results to xargs as it expects them. In the second, the shell is expanding them and passing each match as a separate argument. Both are correct (assuming shell support for **, and that the command line length maximum isn't exceeded).

Related

Easiest way to escape the output of find?

I want to do something like
find . -name "*whatever*" | xargs zip my.zip
but if the files I'm finding contain certain characters, this gets messed up, for example with spaces within the filenames. I guess I should escape the results. I couldn't quite understand from man find whether it could do this for me. So:
Can I make find escape the results?
If not, how should I escape them?
Null separation was made for this exact case.
find can be instructed to separate its outputs with NUL characters (0's) via the -print0 option.
xargs can be instructed that its incoming arguments will be NUL separated with the -0 option.
Hence,
find . -name "*whatever*" -print0 | xargs -0 zip my.zip

using find with variables in bash

I am new to bash scripting and need help:
I need to remove specific files from a directory . My goal is to find in each subdirectory a file called "filename.A" and remove all files that starts with "filename" with extension B,
that is: "filename01.B" , "filename02.B" etc..
I tried:
B_folders="$(find /someparentdirectory -type d -name "*.B" | sed 's# (.*\)/.*#\1#'|uniq)"
A_folders="$(find "$B_folders" -type f -name "*.A")"
for FILE in "$A_folders" ; do
A="${file%.A}"
find "$FILE" -name "$A*.B" -exec rm -f {}\;
done
Started to get problems when the directories name contained spaces.
Any suggestions for the right way to do it?
EDIT:
My goal is to find in each subdirectory (may have spaces in its name), files in the form: "filename.A"
if such files exists:
check if "filename*.B" exists And remove it,
That is: remove: "filename01.B" , "filename02.B" etc..
In bash 4, it's simply
shopt -s globstar nullglob
for f in some_parent_directory/**/filename.A; do
rm -f "${f%.A}"*.B
done
If the space is the only issue you can modify the find inside the for as follows:
find "$FILE" -name "$A*.B" -print0 | xargs -0 rm
man find shows:
-print0
True; print the full file name on the standard output, followed by a null character (instead of the newline character that -print uses). This allows
file names that contain newlines or other types of white space to be correctly interpreted by programs that process the find output. This option corre-
sponds to the -0 option of xargs.
and xarg's manual
-0 Input items are terminated by a null character instead of by whitespace, and the quotes and backslash are not special (every character is taken literal-
ly). Disables the end of file string, which is treated like any other argument. Useful when input items might contain white space, quote marks, or
backslashes. The GNU find -print0 option produces input suitable for this mode.

Find a file and delete the parent level dir

How would it possible to delete the parent dir (only one-level above) where the file is located and is found with find command like
find . -type f -name "*.root" -size 1M
which returns
./level1/level1_chunk84/file.root
So, I want to do actually delete recursively the level_chunck84 dir for example..
thanks
You can try something like:
find . -type f -name "*.root" -size 1M -print0 | \
xargs -0 -n1 -I'{}' bash -c 'fpath={}; rm -r ${fpath%%$(basename {})}'
find + xargs combo is very common. Please refer to man find and you will find a few examples showing how to use them together.
All I did here I simply added -print0 flag to your original find statement:
-print0
True; print the full file name on the standard output, followed by a null character (instead of the newline character that -print
uses). This allows file names that contain newlines or other types of white space to be correctly interpreted by programs that
process the find output. This option corresponds to the -0 option of xargs.
Then piped out everything to xargs which serves as a helper to craft further commands:
- execute everything in bash subshell
- assign file path to a variable fpath={}
- extract dirname from your file path
${parameter%%word}
Remove matching suffix pattern. The word is expanded to produce a pattern just as in pathname expansion. If the pattern matches a
trailing portion of the expanded value of parameter, then the result of the expansion is the expanded value of parameter with the
shortest matching pattern (the %'' case) or the longest matching pattern (the%%'' case) deleted. If parameter is # or *, the
pattern removal operation is applied to each positional parameter in turn, and the expansion is the resultant list. If parameter is
an array variable subscripted with # or *, the pattern removal operation is applied to each member of the array in turn, and the
expansion is the resultant list.
- and finally remove recursively
Also there's a little shorter version of it:
find . -type f -name "*.root" -size 1M -print0 | \
xargs -0 -n1 -I'{}' bash -c 'fpath={}; rm -r ${fpath%/*}'

How can I grep a keyword which contain underscore?

I am currently searching some keyword something like
find -type f | xargs -grep -i -w 'weblogic_*'
But it show all the keyword match with weblogic instead of weblogic_
grep uses regular expressions, not globs (wildcard expressions).
In regular expressions, * is a quantifier that relates to the previous character or expression. Thus, _* is saying: zero or more instances of _, so NO _ will be matched as well.
You probably want:
'weblogic_.*'
which states that any (.) character may follow the _ zero or more times.
Note, however, that ending your regex in _.* partially contradicts grep's -w flag in that it will now only match the beginning of your regex on a word boundary.
If you wanted to be more explicit about this, you could use the word-boundary assertion \b and drop the -w option:
'\bweblogic_'
As you can see, this allows you to omit the .*, as grep performs substring matching by default, and you needn't match the remainder of the line if it is not of interest.
Also, there is no need for xargs: it is simpler and more efficient to use find's -exec primary, which has xargs built in, so to speak:
find . -type f -exec grep -i '\bweblogic_' {} +
{} represents the list of input filenames and + specifies that as many input filenames as possible should be passed at once - as with xargs.
Finally, if your grep version supports the -R option, you can make do without find altogether and simply let grep process all files recursively:
grep -R -i '\bweblogic_' .
When you use the pattern weblogic_*, it means look for weblogic followed by zero or more occurrences of _.
You can change it to use the pattern weblogic_.* if you want to avoid matching weblogic that is not followed by a _.
find -type f | xargs -grep -i -w 'weblogic_.*'
should work.
Try without regex
find . -type f | xargs grep -i 'weblogic_'
simply use this :
grep -i '^weblogic_.*'

Find separates filenames when they contain a space (Shell) [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
So, my program is meant to cycle through a directory and its subdirectories, and when it finds a file that is larger than 100K, asks the user if they want to remove it.
I am using find to cycle through the directories. This is my code:
for file in $(find /home/* -print0 | xargs -0)
I have also tried
for file in $(find /home/* -exec process)
Etc, etc. Pretty much everything on the first five pages of Google.
Just to re-iterate, the problem is that find separates filenames with spaces in them. (i.e. "/home/Old data" would become "/home/Old" and "data"
Anyway, are there any better alternatives that I could be using?
Use a while loop to prevent word splitting:
while read -r file; do
# do something here
echo "${file}" # Remember to quote the variable
done < <(find /path -type f -size +200)
This is a problem with the shell, not with find. devnull has suggested a general fix, but for your specific problem, you can do:
find /path -type f -size +100k -exec ls -lh {} \; -exec rm -i {} \;
For files over 100k, this will list its attributes and ask the user whether to delete.
The process substitution $(...) isn't quoted, so all the filenames come out on one line, separated by spaces. There's no way then to tell which spaces are in filenames and which are separators. But if you quote it, "$(...)", then all the output comes out as a single multi-line string. So doing it this way, passing the command output to a for loop, doesn't work. Instead, use the xargs to do the work.
find /home/* -print0 | xargs -0 -i{} ls -l {}
This works exactly like your find|xargs except that the xargs is given a command to execute. In your case it will be a shell script in a file, e.g. mayberemove.sh. Normally xargs appends as many input lines onto the end of the command as it can, but the -i tells it to run the command with the input line in place of the (arbitrary) string {}. In this case it only uses one input line at a time. Because xargs isn't passing the argument through a shell, but instead runs the command using some variety of exec, there's no need for any more quoting. The -0 means that the arguments are delimited by a null byte, as output by find -print0, to avoid problems with whitespace.
Instead of ls -l, you will want something like:
find /home/* -print0 | xargs -0 -i{} ./mayberemove.sh
where mayberemove.sh is careful to quote its arguments, because it is passing them using the shell:
#!/bin/sh
if ....
then
rm "$1"
fi

Resources