Explain how many processes created? - shell

Could someone answer how many processes are created in each case for the commands below as I dont understand it :
The following three commands have roughly the same effect:
rm $(find . -type f -name '*.o')
find . -type f -name '*.o' | xargs rm
find . -type f -name '*.o' -exec rm {} \;

Exactly 2 processes - 1 for rm, the other for find.
3 or more processes. 1 for find, another for xargs, and one or more rm. xargs will read standard input, and if it reads more lines than can be passed as parameters to a program (There is a maximum value named ARG_MAX).
Many processes, 1 for find and another one for each file ending in .o for rm.
In my opinion, option 2 is the best, because it handles the maximum parameter limit correctly and doesn't spawn too many processes. However, I prefer to use it like this (with GNU find and xargs):
find . -type f -name '*.o' -print0 | xargs -0 rm
This terminates each filename with a \0 instead of a newline, since filenames in UNIX can legally contain newlines. This also handles spaces in filenames (much more common) correctly.

Related

Find command output to echo without variable assignment, in one line

I'm trying to write one line of code that finds all .sh files in the current directory and its subdirectories, and print them without the .sh extension (preferably without the path too).
I think I got the find command down. I tried using the output of
find . -type f -iname "*.sh" -print
as input for echo, and formatting it along these lines
echo "${find_output%.sh}"
However, I cannot get it to work in one line, without variable assigment.
I got inspiration from this answer on stackoverflow https://stackoverflow.com/a/18639136/15124805
to use this line:
echo "${$( find . -type f -iname "*.sh" -print)%.sh}"
But I get this error:
ash: ${$( find . -type f -iname "*.sh" -print)%.sh}: bad substitution
I also tried using xargs
find . -type f -iname "*.sh" -print |"${xargs%.sh}" echo
But I get a "command not found error" -probably I didn't use xargs correctly, but I'm not sure how I could improve this or if it's the right way to go.
How can I make this work?
That's the classic useless use of echo. You simply want
find . -type f -iname "*.sh" -exec basename {} .sh \;
If you have GNU find, you can also do this with -printf.
However, basename only matches .sh literally, so if you really expect extensions with different variants of capitalization, you need a different approach.
For the record, the syntax you tried to use for xargs would attempt to use the value of a variable named xargs. The correct syntax would be something like
find . -type f -iname "*.sh" -print |
xargs -n 1 sh -c 'echo "${1%.[Ss][Hh]}"' _
but that's obviously rather convoluted. In some more detail, you need sh because the parameter expansion you are trying to use is a feature of the shell, not of echo (or xargs, or etc).
(You can slightly optimize by using a loop:
find . -type f -iname "*.sh" -print |
xargs sh -c 'for f; do
echo "${f%.[Ss][Hh]}"
done' _
but this is still not robust for all file names; see also https://mywiki.wooledge.org/BashFAQ/020 for probably more than you realized you needed to know about this topic. If you have GNU find and GNU xargs, you can use find ... -print0 | xargs -r0)

How to find basename of path via pipe

This doesn't work:
find "$all_locks" -mindepth 1 -maxdepth 1 -type d | basename
apparently basename cannot read from stdin - in any case basename requires at least one argument.
To apply a command to every result of a piped operation, xargs is your friend. As it says on the man page I linked...
xargs reads items from the standard input, delimited by blanks (which
can be protected with double or single quotes or a backslash) or
newlines, and executes the command (default is /bin/echo) one or more
times with any initial-arguments followed by items read from standard
input.
In this case that means it will take each result from your find command and run basename <find result>ad nauseum, until find has completed its search. I believe what you want is going to look a lot like this:
find "$all_locks" -mindepth 1 -maxdepth 1 -type d | xargs basename
Since mindepth and maxdepth are GNU extensions, using another one such as printf will not make it less portable.
find "$all_locks" -mindepth 1 -maxdepth 1 -type d -printf '%f\n'
The problem here is basename doesn't accept the stdin and hence unnamed pipes may not be useful. I would like to modify your command a little bit. Let me know if it serves the purpose.
find -mindepth 1 -maxdepth 1 -type d -exec basename {} \;
Note: Not enough reputation to comment, hence posting it here.

Ignore spaces in Solaris 'find' output

I am trying to remove all empty files that are older than 2 days. Also I am ignoring hidden files, starting with dot. I am doing it with this code:
find /u01/ -type f -size 0 -print -mtime +2 | grep -v "/\\." | xargs rm
It works fine until there are spaces in the name of the file. How could I make my code ignore them?
OS is Solaris.
Option 1
Install GNU find and GNU xargs in an appropriate location (not /usr/bin) and use:
find /u01/ -type f -size 0 -mtime +2 -name '[!.]*' -print0 | xargs -0 rm
(Note that I removed (what I think is) a stray -print from your find options. The options shown removes empty files modified more than 2 days ago where the name does not start with a ., which is the condition that your original grep seemed to deal with.)
Option 2
The problem is primarily that xargs is defined to split its input at spaces. An alternative is to write your own xargs surrogate that behaves sensibly with spaces in names; I've done that. You then only run into problems if the file names contain newlines — which the file system allows. Using a NUL ('\0') terminator is guaranteed safe; it is the only character that can't appear in a path name (which is why GNU chose to use it with -print0 etc).
Option 3
A final better option is perhaps:
find /u01/ -type f -size 0 -mtime +2 -name '[!.]*' -exec rm {} \;
This avoids using xargs at all and handles all file names (path names) correctly — at the cost of executing rm once for each file found. That's not too painful if you're only dealing with a few files on each run.
POSIX 2008 introduces the notation + in place of the \; and then behaves rather like xargs, collecting as many arguments as will conveniently fit in the space it allocates for the command line before running the command:
find /u01/ -type f -size 0 -mtime +2 -name '[!.]*' -exec rm {} +
The versions of Solaris I've worked on do not support that notation, but I know I work on antique versions of Solaris. GNU find does support the + marker and therefore renders the -print0 and xargs -0 workaround unnecessary.

Moving multiple files in subdirectories (and/or splitting strings by multichar delimeter) [bash]

So basically, I have a folder with a bunch of subfolders all with over 100 files in them. I want to take all of the mp3 files (really generic extension since I'll have to do this with jpg, etc.) and move them to a new folder in the original directory. So basically the file structure looks like this:
/.../dir/recup1/file1.mp3
/.../dir/recup2/file2.mp3
... etc.
and I want it to look like this:
/.../dir/music/file1.mp3
/.../dir/music/file2.mp3
... etc.
I figured I would use a bash script that looked along these lines:
#!/bin/bash
STR=`find ./ -type f -name \*.mp3`
FILES=(echo $STR | tr ".mp3 " "\n")
for x in $FILES
do
echo "> [$x]"
done
I just have it echo for now, but eventually I would want to use mv to get it to the correct folder. Obviously this doesn't work though because tr sees each character as a delimiter, so if you guys have a better idea I'd appreciate it.
(FYI, I'm running netbook Ubuntu, so if there's a GUI way akin to Windows' search, I would not be against using it)
If the music folder exists then the following should work -
find /path/to/search -type f -iname "*.mp3" -exec mv {} path/to/music \;
A -exec command must be terminated with a ; (so you usually need to type \; or ';' to avoid interpretion by the shell) or a +. The difference is that with ;, the command is called once per file, with +, it is called just as few times as possible (usually once, but there is a maximum length for a command line, so it might be split up) with all filenames.
You can do it like this:
find /some/dir -type f -iname '*.mp3' -exec mv \{\} /where/to/move/ \;
The \{\} part will be replaced by the found file name/path. The \; part sets the end for the -exec part, it can't be left out.
If you want to print what was found, just add a -print flag like:
find /some/dir -type f -iname '*.mp3' -print -exec mv \{\} /where/to/move/ \;
HTH

Piping find to find

I want to pipe a find result to a new find. What I have is:
find . -iname "2010-06*" -maxdepth 1 -type d | xargs -0 find '{}' -iname "*.jpg"
Expected result: Second find receives a list of folders starting with 2010-06, second find returns a list of jpg's contained within those folders.
Actual result: "find: ./2010-06 New York\n: unknown option"
Oh darn. I have a feeling it concerns the format of the output that the second find receives as input, but my only idea was to suffix -print0 to first find, with no change whatsoever.
Any ideas?
You need 2 things. -print0, and more importantly -I{} on xargs, otherwise the {} doesn't do anything.
find . -iname "2010-06*" -maxdepth 1 -type d -print0 | xargs -0 -I{} find '{}' -iname '*.jpg'
Useless use of xargs.
find 2010-06* -iname "*.jpg"
At least Gnu-find accepts multiple paths to search in. -maxdepth and type -d is implicitly assumed.
How about
find . -iwholename "./2010-06*/*.jpg
etc?
Although you did say that you specifically want this find + pipe problem to work, its inefficient to fork an extra find command. Since you are specifying -maxdepth as 1, you are not traversing subdirectories. So just use a for loop with shell expansion.
for file in *2010-06*/*.jpg
do
echo "$file"
done
If you want to find all jpg files inside each 2010-06* folders recursively, there is also no need to use multiple finds or xargs
for directory in 2010-06*/
do
find $directory -iname "*.jpg" -type f
done
Or just
find 2006-06* -type f -iname "*.jpg"
Or even better, if you have bash 4 and above
shopt -s globstar
shopt -s nullglob
for file in 2010-06*/**/*.jpg
do
echo "$file"
done

Resources