Handling spaces in a directory in bash sh - bash

I have this line cp $(find "$directory" -type f | grep -iE '\.(c|cc|cpp|cxx)$') ~/src which searches a given directory (in this case, $directory is /home) and copies all file with the extensions of .c, .cc, .cpp and .cxx into the src folder, but I get an error of cp:cannot stat directory: No such file or directory.
I thought putting the directory in quotes would prevent that. What am I doing wrong?

The error is from the command cp, so quoting $directory, while generally a good idea, won't help you solve this error.
Your construct will fail with file/directory names that contain spaces, cases where grep turns out with zero matches, and probably other cases I can't think of right now.
Some better solutions:
Use find's name matching instead of grep, and use -exec with it:
find "$directory" -type f \( -name '*.c' -o -name '*.cc' -o -name '*.cpp' -o -name '*.cxx' \) -exec cp '{}' ~/src ';'
find "$directory" -type f -regextype posix-egrep -iregex '.*\.(c|cc|cpp|cxx)$' -exec cp '{}' ~/src ';'
Use xargs with \0 separators instead of \n:
find "$directory" -type f -print0 | grep -z -iE '\.(c|cc|cpp|cxx)$' | xargs -0 -I{} cp "{}" ~/src
If your file structure is flat (no subdirectories), just use cp:
cd "$directory"; cp *.c *.cc *.cpp *.cxx ~/src

Related

unix find in script which needs to met a combination of condiations and execute something for all found files

Let's assume we have the following files and directories:
-rw-rw-r--. a.c
-rw-rw-r--. a.cpp
-rw-rw-r--. a.h
-rw-rw-r--. a.html
drwxrwxr-x. dir.c
drwxrwxr-x. dir.cpp
drwxrwxr-x. dir.h
I want to execute grep on all the files (it should also look in subdirectories) which meet the following conditions:
Only files, not directories.
Only files which end with .h, .c and .cpp.
On files found, execute grep and print the file name where grep finds something.
But this ends up in a very long command with a lot of repetitions like:
find . -name '*.c' -type f -exec grep hallo {} \; -print -o -name '*.cpp' -type f -exec grep hallo {} \; -print -o -name '*.h' -type f -exec grep hallo {} \; -print
Can the conditions be grouped to remove the repetitions or are there any other possible simplifications?
My system is Fedora 33, with GNU grep, GNU find and bash available.
With Bash's extglob and globstar special options:
shopt +s extglob globstar
grep -s regex **/*.+(cpp|c|h)
You can put the first line in ~/.bashrc so you don't need to manually enable them options in every shell.
If you happened to have directories with those extensions, Grep would complain without the -s flag.
In GNU Find, use the -regex option to make it easier:
find . -type f -regex '.*\.\(c\|h\|cpp\)' -exec grep regex {} +
find . -type f -regextype awk -regex '.*\.(c|h|cpp)' -exec grep regex {} +
More on Find's regextypes.
With POSIX tools only:
find . -type f \( -name '*.[ch]' -o -name '*.cpp' \) -exec grep regex {} +
You can use regex/regextype instead of name and so:
find . -regextype "posix-extended" -regex "^.*\.((c)|(cpp)|(h))$" -exec grep hallo '{}' \;

Delete directories with specific files in Bash Script

I would like to delete specific files if existed but also the directories that contain these files. I do know the files I would like to wipe but not the directories. So far, as I'm new in bash scripting, I think of this :
find ./ -type f -name '*.r*' -print0 | xargs -0 rm -rf &> log_del.txt
find ./ -type f -name '*.c*' -print0 | xargs -0 rm -rf &>> log_del.txt
At the moment, all files named with the specific extensions *.r* and *.c* are deleted.
But the directories are still remaining and also the subdirectories in it, if existed.
I also thought of the option -o in find to delete in one line :
find ./ -type f \( -name '*.r*' -o -name '*.c*' \) -print0 | xargs -0 rm -rf &> log_del.txt
How can I do this?
And I also see that my log_del.txt file is empty... :-(
It looks like what you really want is to remove all empty directories, recursively.
find . -type d -delete
-delete processes the directories in child-first order, so that a/b is deleted before a. If a given directory is not empty, find will just display an error and continue.
If the directories remain empty, let rmdir try to remove all of them. It will fail on any directories which have still files.
find ./ -type d -exec rmdir --ignore-fail-on-non-empty {} 2>/dev/null \;
See if this serves your requirement:
find ./ -type f -name '*.r*' -delete -printf "%h\0" | xargs -0 rmdir
If the directory contained any other files, rmdir will fail.
So consider below sample file structure:
$ find a
a/
a/a/
a/a/4
a/b/
a/b/5
a/b/4
a/b/3
a/b/2
a/b/1
$ find a -type f -name '4' -delete -printf "%h\0" | xargs -0 -r rmdir
rmdir: failed to remove ‘a/b’: Directory not empty
$ find a
a
a/b
a/b/5
a/b/3
a/b/2
a/b/1
If in above example, you want to delete directory b also, you can simply use:
$ find ./ -type f -name '*.r*' -printf "%h\0" | xargs -0 rm -rf
EDIT: As per the comment, you (OP) wanted that the empty directory tree should also be deleted. These 2 commands should help you then:
$ find ./ -type f -name '*.r*' -delete # Delete matching files
$ find ./ -empty -type d -delete # Delete tree of empty directories

BASH: find and rename files & directories

I would like to replace :2f with a - in all file/dir names and for some reason the one-liner below is not working, is there any simpler way to achieve this?
Directory name example:
AN :2f EXAMPLE
Command:
for i in $(find /tmp/ \( -iname ".*" -prune -o -iname "*:*" -print \)); do { mv $i $(echo $i | sed 's/\:2f/\-/pg'); }; done
You don't have to parse the output of find:
find . -depth -name '*:2f*' -execdir bash -c 'echo mv "$0" "${0//:2f/-}"' {} \;
We're using -execdir so that the command is executed from within the directory containing the found file. We're also using -depth so that the content of a directory is considered before the directory itself. All this to avoid problems if the :2f string appears in a directory name.
As is, this command is harmless and won't perform any renaming; it'll only show on the terminal what's going to be performed. Remove echo if you're happy with what you see.
This assumes you want to perform the renaming for all files and folders (recursively) in current directory.
-execdir might not be available for your version of find, though.
If your find doesn't support -execdir, you can get along without as so:
find . -depth -name '*:2f*' -exec bash -c 'dn=${0%/*} bn=${0##*/}; echo mv "$dn/$bn" "$dn/${bn//:2f/-}"' {} \;
Here, the trick is to separate the directory part from the filename part—that's what we store in dn (dirname) and bn (basename)—and then only change the :2f in the filename.
Since you have filenames containing space, for will split these up into separate arguments when iterating. Pipe to a while loop instead:
find /tmp/ \( -iname ".*" -prune -o -iname "*:*" -print \) | while read -r i; do
mv "$i" "$(echo "$i" | sed 's/\:2f/\-/pg')"
Also quote all the variables and command substitutions.
This will work as long as you don't have any filenames containing newline.

removing files that have extension .bin then printing bye

filename=file.bin
extension=$(echo ${filename}|awk -F\. '{print $2}')
if [ ${extension} == "bin" ]; then
rm *.extenstion
fi
would something like this work how do I delete all files that have the same extention in a folder
You don't need to extract the extension yourself, this is what globbing is for. Simply do:
rm *.bin
Or recursively find ./ -name "*.bin" -exec rm -f {} \;
Aside from globbing, this is also doable with find.
find -type f -name "*.bin" -exec rm {} \;
Or more efficiently, with newer version of find:
find -type f -name "*.bin" -exec rm {} +
which is equivalent to
find -type f -name "*.bin" | xargs rm
Note: by default, find will do it recursively.

How to run a command recursively on all files except for those under .svn directories

Here is how i run dos2unix recursively on all files:
find -exec dos2unix {} \;
What do i need to change to make it skip over files under .svn/ directories?
Actual tested solution:
$ find . -type f \! -path \*/\.svn/\* -exec dos2unix {} \;
Here's a general script on which you can change the last line as required.
I've taken the technique from my findrepo script:
repodirs=".git .svn CVS .hg .bzr _darcs"
for dir in $repodirs; do
repo_ign="$repo_ign${repo_ign+" -o "}-name $dir"
done
find \( -type d -a \( $repo_ign \) \) -prune -o \
\( -type f -print0 \) |
xargs -r0 \
dos2unix
Just offering an additional tip: piping the result through xargs instead of using find's -exec option will increase the performance when going through a large directory structure if the filtering program accepts multiple arguments, as this will reduce the number of fork()'s, so:
find <opts> | xargs dos2unix
One caveat: piping through xargs will fail horribly if any filenames include whitespace.
In bash
for fic in **/*; dos2unix $fic
Or even better in zsh
for fic in **/*(.); dos2unix $fic
find . -path ./.svn -prune -o -print0 | xargs -0 -i echo dos2unix "{}" "{}"
if you have bash 4.0
shopt -s globstar
shopt -s dotglob
for file in /path/**
do
case "$file" in
*/.svn* )continue;;
esac
echo dos2unix $file $file
done

Resources