Deleting all the subfolders having fewer than X files - bash

I would like to remove all the subfolders having less than X file in a folder
The following code search those subfolders with less than X file:
$ find . -type d -exec sh -c 'set -- "$0"/*; [ $# -le 10 ]' {} \; -print
./digna_1919
./digna_2040
./digna_1682
(more output omitted)
So I can find them! But if I do this, I get "Directory not empty":
$ find . -type d -exec sh -c 'set -- "$0"/*; [ $# -le 10 ]' {} \; -delete
find: cannot delete ‘./digna_1919’: Directory not empty
find: cannot delete ‘./digna_2040’: Directory not empty
find: cannot delete ‘./digna_1682’: Directory not empty
(more output omitted)
And if I do this, I get "No such file or directory":
$ find . -type d -exec sh -c 'set -- "$0"/*; [ $# -le 10 ]' {} \; -exec rm -r "{}" \;
find: ‘./digna_1919’: No such file or directory
find: ‘./digna_2040’: No such file or directory
find: ‘./digna_1682’: No such file or directory
(more output omitted)
Where am I doing wrong? Thanks a lot!
Source of the code

Use -depth to have find process each directory's contents before the directory itself. You'll want to do this any time you delete items.
$ find . -depth -type d -exec sh -c 'set -- "$0"/*; [ $# -le 10 ]' {} \; -exec rm -r {} \;

Related

Rename file if it is the only one with the extension in directory

This works however I would like to do it only if it is the only .jpg for the given directory, the one below will just rename them all to folder.jpg, overwriting the other files:
find . -type f -name '*.jpg' -execdir mv {} 'folder.jpg' \;
I guess find cannot filter by the number of matches, but you can always exec a shell which does more elaborate checks for you:
find . -type f -name '*.jpg' -execdir sh -c '[ $# = 1 ] && mv "$1" folder.jpg' sh {} +

find: How to use found paths in the -exec directive?

I have a dozen files named
~/DOMAIN1.de/bin/dbdeploy.php
~/DOMAIN2.de/bin/dbdeploy.php
~/DOMAIN3.de/bin/dbdeploy.php
I want to run them all with the same arguments.
My bash script reads:
cd ~
find . -maxdepth 1 -type d -name "*\.de" -exec php56 bin/dbdeploy.php "$1" "$2" \;
However, the path given to exec seems not to be relative to the found subdirectory but rather to my PWD:
$ bash -x ./.dbpush "some argument"
+ cd ~
+ find . -maxdepth 1 -type d -name '*\.de' -exec php56 bin/dbdeploy.php 'some argument' ';'
Could not open input file: bin/dbdeploy.php
Could not open input file: bin/dbdeploy.php
Could not open input file: bin/dbdeploy.php
How can I use the found path in the -exec directive?
Ok, actually I found the answer myself:
The "find"-results are stored in {}, so the line reads
find . -maxdepth 1 -type d -name "*\.de" -exec php56 {}/bin/dbdeploy.php "$1" "$2" \;
Alternativly
find . -type f -wholename "*\.de/bin/dbdeploy.php" -exec php56 {} "$1" "$2" \;

Bash - create subfolders for folders recently created

I want to "join" these two tasks:
for dir in /blabla/bleble/*; do (cd "$dir" && mkdir -p Folder1/Folder1a && mkdir -p Folder2); done
and
find -amin -10
How can I do this?
I've tried this, but it doesn't work:
find -amin -2 -exec sh -c '
for dir in /blabla/bleble/*; do (cd "$dir" && mkdir -p Folder1/Folder1a && mkdir -p Folder2);
done' sh {} +
Something like this is might do:
find /a/b/ -mindepth 1 -maxdepth 1 -type d -amin -2 \
-exec sh -c 'for f; do mkdir -p -- "$f/Folder1/Folder1a" "$f/Folder2; done"' "" {} +
Breakdown:
-mindepth 1 -maxdepth 1 will limit result to current directory only.
-type d makes sure you only list directories
-exec foo "" {} + will execute foo with matches as arguments:
foo "" "/a/b/c" "/a/b/john" "a/b/doe"
for f will iterate over all positional arguments ($1, $2, ...)
for f; do
mkdir -p -- "$f/Folder1/Folder1a" "$f/Folder2"
done
Running sh -c 'code' arg1 arg2 will set $0 to arg1, and $1 to arg2, therefore the empty argument: foo "" {} +:
% sh -c 'echo $0' john
john
Assuming there aren't so many folders in /blabla/bleble that you overflow the command line, you can use find to search the target directory. -prune prevents recursing into the directories.
find /blabla/bleble/* -prune -type d -amin -10 -exec mkdir -p {}/Folder1/Folder1a {}/Folder2 \;
If you are using GNU find or another version that supports them, use -mindepth and -maxdepth instead to find the top-level subdirectories, no matter how many there are.
find /blabla/bleble -maxdepth 1 -mindepth 1 -type d -amin -10 -exec mkdir -p {}/Folder1/Folder1a {}/Folder2 \;

find -exec with multiple commands

I am trying to use find -exec with multiple commands without any success. Does anybody know if commands such as the following are possible?
find *.txt -exec echo "$(tail -1 '{}'),$(ls '{}')" \;
Basically, I am trying to print the last line of each txt file in the current directory and print at the end of the line, a comma followed by the filename.
find accepts multiple -exec portions to the command. For example:
find . -name "*.txt" -exec echo {} \; -exec grep banana {} \;
Note that in this case the second command will only run if the first one returns successfully, as mentioned by #Caleb. If you want both commands to run regardless of their success or failure, you could use this construct:
find . -name "*.txt" \( -exec echo {} \; -o -exec true \; \) -exec grep banana {} \;
find . -type d -exec sh -c "echo -n {}; echo -n ' x '; echo {}" \;
One of the following:
find *.txt -exec awk 'END {print $0 "," FILENAME}' {} \;
find *.txt -exec sh -c 'echo "$(tail -n 1 "$1"),$1"' _ {} \;
find *.txt -exec sh -c 'echo "$(sed -n "\$p" "$1"),$1"' _ {} \;
Another way is like this:
multiple_cmd() {
tail -n1 $1;
ls $1
};
export -f multiple_cmd;
find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;
in one line
multiple_cmd() { tail -1 $1; ls $1 }; export -f multiple_cmd; find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;
"multiple_cmd()" - is a function
"export -f multiple_cmd" - will export it so any other subshell can see it
"find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;" - find that will execute the function on your example
In this way multiple_cmd can be as long and as complex, as you need.
Hope this helps.
There's an easier way:
find ... | while read -r file; do
echo "look at my $file, my $file is amazing";
done
Alternatively:
while read -r file; do
echo "look at my $file, my $file is amazing";
done <<< "$(find ...)"
Extending #Tinker's answer,
In my case, I needed to make a command | command | command inside the -exec to print both the filename and the found text in files containing a certain text.
I was able to do it with:
find . -name config -type f \( -exec grep "bitbucket" {} \; -a -exec echo {} \; \)
the result is:
url = git#bitbucket.org:a/a.git
./a/.git/config
url = git#bitbucket.org:b/b.git
./b/.git/config
url = git#bitbucket.org:c/c.git
./c/.git/config
I don't know if you can do this with find, but an alternate solution would be to create a shell script and to run this with find.
lastline.sh:
echo $(tail -1 $1),$1
Make the script executable
chmod +x lastline.sh
Use find:
find . -name "*.txt" -exec ./lastline.sh {} \;
Thanks to Camilo Martin, I was able to answer a related question:
What I wanted to do was
find ... -exec zcat {} | wc -l \;
which didn't work. However,
find ... | while read -r file; do echo "$file: `zcat $file | wc -l`"; done
does work, so thank you!
1st answer of Denis is the answer to resolve the trouble. But in fact it is no more a find with several commands in only one exec like the title suggest. To answer the one exec with several commands thing we will have to look for something else to resolv. Here is a example:
Keep last 10000 lines of .log files which has been modified in the last 7 days using 1 exec command using severals {} references
1) see what the command will do on which files:
find / -name "*.log" -a -type f -a -mtime -7 -exec sh -c "echo tail -10000 {} \> fictmp; echo cat fictmp \> {} " \;
2) Do it: (note no more "\>" but only ">" this is wanted)
find / -name "*.log" -a -type f -a -mtime -7 -exec sh -c "tail -10000 {} > fictmp; cat fictmp > {} ; rm fictmp" \;
I usually embed the find in a small for loop one liner, where the find is executed in a subcommand with $().
Your command would look like this then:
for f in $(find *.txt); do echo "$(tail -1 $f), $(ls $f)"; done
The good thing is that instead of {} you just use $f and instead of the -exec … you write all your commands between do and ; done.
Not sure what you actually want to do, but maybe something like this?
for f in $(find *.txt); do echo $f; tail -1 $f; ls -l $f; echo; done
should use xargs :)
find *.txt -type f -exec tail -1 {} \; | xargs -ICONSTANT echo $(pwd),CONSTANT
another one (working on osx)
find *.txt -type f -exec echo ,$(PWD) {} + -exec tail -1 {} + | tr ' ' '/'
A find+xargs answer.
The example below finds all .html files and creates a copy with the .BAK extension appended (e.g. 1.html > 1.html.BAK).
Single command with multiple placeholders
find . -iname "*.html" -print0 | xargs -0 -I {} cp -- "{}" "{}.BAK"
Multiple commands with multiple placeholders
find . -iname "*.html" -print0 | xargs -0 -I {} echo "cp -- {} {}.BAK ; echo {} >> /tmp/log.txt" | sh
# if you need to do anything bash-specific then pipe to bash instead of sh
This command will also work with files that start with a hyphen or contain spaces such as -my file.html thanks to parameter quoting and the -- after cp which signals to cp the end of parameters and the beginning of the actual file names.
-print0 pipes the results with null-byte terminators.
for xargs the -I {} parameter defines {} as the placeholder; you can use whichever placeholder you like; -0 indicates that input items are null-separated.
I found this solution (maybe it is already said in a comment, but I could not find any answer with this)
you can execute MULTIPLE COMMANDS in a row using "bash -c"
find . <SOMETHING> -exec bash -c "EXECUTE 1 && EXECUTE 2 ; EXECUTE 3" \;
in your case
find . -name "*.txt" -exec bash -c "tail -1 '{}' && ls '{}'" \;
i tested it with a test file:
[gek#tuffoserver tmp]$ ls *.txt
casualfile.txt
[gek#tuffoserver tmp]$ find . -name "*.txt" -exec bash -c "tail -1 '{}' && ls '{}'" \;
testonline1=some TEXT
./casualfile.txt
Here is my bash script that you can use to find multiple files and then process them all using a command.
Example of usage. This command applies a file linux command to each found file:
./finder.sh file fb2 txt
Finder script:
# Find files and process them using an external command.
# Usage:
# ./finder.sh ./processing_script.sh txt fb2 fb2.zip doc docx
counter=0
find_results=()
for ext in "${#:2}"
do
# #see https://stackoverflow.com/a/54561526/10452175
readarray -d '' ext_results < <(find . -type f -name "*.${ext}" -print0)
for file in "${ext_results[#]}"
do
counter=$((counter+1))
find_results+=("${file}")
echo ${counter}") ${file}"
done
done
countOfResults=$((counter))
echo -e "Found ${countOfResults} files.\n"
echo "Processing..."
counter=0
for file in "${find_results[#]}"
do
counter=$((counter+1))
echo -n ${counter}"/${countOfResults}) "
eval "$1 '${file}'"
done
echo "All files have been processed."

modify shell script to delete folders as well as files

My shell script:
#!/bin/bash
if [ $# -lt 2 ]
then
echo "$0 : Not enough argument supplied. 2 Arguments needed."
echo "Argument 1: -d for debug (lists files it will remove) or -e for execution."
echo "Followed by some path to remove files from. (path of where to look) "
exit 1
fi
if test $1 == '-d'
then
find $2 -mmin +60 -type f -exec ls -l {} \;
elif test $1 == '-e'
then
find $2 -mmin +60 -type f -exec rm -rf {} \;
fi
Basically this will find files in a given directory provided as second argument and either list (-d for argument 1) or remove (-e for argument 1) files modified >60 minutes ago.
How can I rework this to also remove folders ?
Remove -type f
changing ls -l to ls -ld
Change 1 will list everything and not just files. This includes links as well. If you are not fine with listing/deleting anything other than files and directories then you need to separately list/delete files and directories as:
if test $1 == '-d'
then
find $2 -mmin +60 -type f -exec ls -ld {} \;
find $2 -mmin +60 -type d -exec ls -ld {} \;
elif test $1 == '-e'
then
find $2 -mmin +60 -type f -exec rm -rf {} \;
find $2 -mmin +60 -type d -exec rm -rf {} \;
fi
Change 2 is needed as ls -l on a directory will list the files in the directories.
#!/bin/bash
if [ $# -lt 2 ]
then
echo "$0 : Not enough argument supplied. 2 Arguments needed."
echo "Argument 1: -d for debug (lists files it will remove) or -e for execution."
echo "Followed by some path to remove files from. (path of where to look) "
exit 1
fi
if test $1 == '-d'
then
find $2 -mmin +60 -type d -exec ls -l {} \;
find $2 -mmin +60 -type f -exec ls -l {} \;
elif test $1 == '-e'
then
find $2 -mmin +60 -type d -exec rm -rf {} \;
find $2 -mmin +60 -type f -exec rm -rf {} \;
fi
That should work for you.

Resources