delete directories not in the file containing directory names list - bash

I have a file having the list of directory name I want to keep. Say file1 and its contents are names of directories like
dir1
dir2
dir3
My directory (actual directories) on the other hand has directories like
dir1
dir2
dir3
dir4
dirs
What I want to do is delete dir4, dirs and other directories of which their name doesn't exist on file1 from My directory. file1 has a directory name per line. There might be sub directories or files under dir4 and dirs which needs a recursive deletion.
I can use xargs to delete the files in the list within My directory
xargs -a file1 rm -r
But instead of removing, I want to keep them and remove the others which are not on file1. Can do
xargs -a file1 mv -t /home/user1/store/
And delete the remaining directories in my directory but I am wandering if there is a better way?
Thanks.

find . -maxdepth 1 -type d -path "./*" -exec sh -c \
'for f; do f=${f#./}; grep -qw "$f" file1 || rm -rf "$f"; done' sh {} +

Anish has a great one-liner answer for you. If you wanted something verbose that can help you in the future with data manipulation or such, here's a verbose version:
#!/bin/bash
# send this function the directory name
# it compares that name with all entries in
# file1. If entry is found, 0 is returned
# That means...do not delete directory
#
# Otherwise, 1 is returned
# That means...delete the directory
isSafe()
{
# accept the directory name parameter
DIR=$1
echo "Received $DIR"
# assume that directory will not be found in file list
IS_SAFE=1 # false
# read file line by line
while read -r line; do
echo "Comparing $DIR and $line."
if [ $DIR = $line ]; then
IS_SAFE=0 # true
echo "$DIR is safe"
break
fi
done < file1
return $IS_SAFE
}
# find all files in current directory
# and loop through them
for i in $(find * -type d); do
# send each directory name to function and
# capture the output with $?
isSafe $i
SAFETY=$?
# decide whether to delete directory or not
if [ $SAFETY -eq 1 ]; then
echo "$i will be deleted"
# uncomment below
# rm -rf $i
else
echo "$i will NOT be deleted"
fi
echo "-----"
done

you can exclude your directories using grep:
find . -mindepth 1 -maxdepth 1 -type d -printf '%P\n' | grep -f file1 -Fx -v | xargs rm -r
-printf '%P\n' is used in order to remove leading './' from directory names.
From man find, description of -printf format:
%P     File's name with the name of the starting-point under which it was found removed.
grep parameters:
-f FILE   Obtain patterns from FILE, one per line.
-F     Interpret PATTERNS as fixed strings, not regular expressions.
-x     Select only those matches that exactly match the whole line. For a regular expression pattern, this is like parenthesizing the pattern and then surrounding it with ^ and $.
-v     Invert the sense of matching, to select non-matching lines.

Related

How to replace the position of a directory containing spaces

I made two pipelines; this, x="$(ls -1p | grep "/$" | tr -d "/")" get all the sub directories from the working directory, and this, y="$(ls -1p | grep "/$"| grep \ | tr -d "/")" gets the sub directories that contain spaces in the working directory.
So now what I've trying to do is to replace the position of the directory that contains spaces and puts it at the very top, ie., say below are my sub dirs:
Dir1
Dir2
Dir 3
Now Dir 3 goes to the top:
Dir 3
Dir1
Dir1
for I in $x; do
for X in $y; do
if [[ $I == $X ]];then
sed "/"$X"/d" "$I"
fi
done
echo "$I"
done
Above is my loop to do that task. It prints all the sub dirs that contains no spaces but prints it as:
Dir1
Dir2
sed: Dir: No such file or directory
Dir
sed: 3: No such file or directory
3
If anyone can help out that will be greatly appreciated.
If you prefer for loop to the find command, how about:
#!/bin/bash
# 1st loop to print the dirnames containing space character
for d in */; do # loops over subdirectories under current directory
if [[ $d =~ [[:space:]] ]]; then # if the dirname contains a space character
echo "${d%/}" # then print the name removing the trailing slash
fi
done
# 2nd loop to print the dirnames without space character
for d in */; do
if [[ ! $d =~ [[:space:]] ]]; then # if the dirname does not contain a space character
echo "${d%/}"
fi
done
Output with the provided example:
Dir 3
Dir1
Dir2
Using GNU find:
find . -mindepth 1 -type d -name '*[[:space:]]*' # spaces
find . -mindepth 1 -type d -regex '.*/[^/[:space:]]+$' # no spaces
This is recursive.

Copy file into directories, and change text inside file to match index of directory

I have the following files in a directory: text.txt and text2.txt
My goal is to:
1) copy these two files into non-existing directories m06/, m07/...m20/.
2) Then, in the file text.txt, in the line containing the string mlist 06 (all the files will contain such a string), I wish to change the "06" to match the index of the directory name (for example, in m13, that line in the text.txt file would be mlist 13.
For goal 1), I got the following script which works succesfully:
#!/bin/bash
mkdir $(printf "m%02i " $(seq 6 20))
find . -maxdepth 1 -type d -name 'm[0-9][0-9]' -print0 | xargs -0 -I {} cp text.txt {}
find . -maxdepth 1 -type d -name 'm[0-9][0-9]' -print0 | xargs -0 -I {} cp text2.txt {}
For goal 2), I wish to implement a command similar to
sed -i -e '/mlist/s/06/index/' ./*/text.inp
where index would correspond to the name of the directory (i.e. index = 13 in the m13/directory).
How can I make the sed command replace 06 with the correct "index" corresponding to the name of the directory?
This would probably be easier to manage if you used loop syntax instead of one-liners:
#!/bin/sh
for i in $(seq 6 20); do
# Add a leading 0 and generate the directory name
i_z=$(printf "%02d" "$i")
dir="m${i_z}"
# Create dir
mkdir -p "$dir"
# Copy base files into dir
cp test.txt test2.txt "$dir"
# Edit the index in the files to match the dir index
sed -i -e "s/mlist.*/mlist $i_z/g" \
"${dir}/test.txt" "${dir}/test2.txt"
done

rename recursively adding parenthere if folder name ending with 4 digits in bash

I have been trying to recursively rename folders whose names ends in four digits.
For example, I have a folder name like this:
this is the name 2004
and I'm trying to rename it to:
this is the name (2004)
I've tried to split the prefix and digit parts of the name however I cannot mv as rename these folder.
Here is the code I've tried so far:
#!/bin/bash
F=$(find . -name '*[0-9]' -type d)
for i in "$F";
do
R2=$(echo "$i" | awk '{print $NF}')
R1=$(echo "$i" | sed 's/.\{4\}$//')
R3=$(echo "$R2" | sed -r "s/(^[0-9]+$)/(\1)/g")
mv "$i" "$R1 $R3"
# Even tried:
mv "\"$i"\" "\"$R2 $R3"\"
done
Does anyone can review or/and suggest some guidance to allow mv to find the initial folder and its destination?
following command:
find -name '*[0-9][0-9][0-9][0-9]' -type d -exec bash -c 'for dir; do mv "$dir" "${dir%[0-9][0-9][0-9][0-9]}(${dir#${dir%[0-9][0-9][0-9][0-9]}})"; done' - {} + -prune
should work.
double quote arround variable expansion
${dir%[0-9][0-9][0-9][0-9]} to remove last 4 digits suffix
${dir#${dir%[0-9][0-9][0-9][0-9]}} to remove previous prefix
-exec bash -c '..' - {} + the - to skip the first argument after -c command which is taken for $0, see man bash /-c
-prune at the end to prevent to search in sub tree when matched, (suppose 2004/2004 then mv 2004/2004 "2004/(2004)" or mv 2004/2004 (2004)/2004' would fail)
I found Bash annoying when it comes to find and rename files for all the escaping one needs to make. This is a cleaner Ruby solution :
#!/usr/bin/ruby
require 'fileutils'
dirs = Dir.glob('./**/*').select {|x| x =~ / [0-9]*/ }
dirs.sort().reverse().each do |dir|
new_name=dir.gsub(/(.*)( )([0-9]{4})/, '\1\2(\3)')
FileUtils.mv dir,new_name
end
When $F has more than one directoy, the for loop will consider it as a one long entry with newlines (try echo "F=[$F]").
Also use -depth, you might have topdir 2004/subdir 2004. So first rename the subdir.
When the directories don't have newlines, you can try
#!/bin/bash
while IFS= read -r orgdir; do
mv "${orgdir}" "$(sed -r "s/([0-9]+$)/(\1)/g" <<< "${orgdir}")"
done < <(find . -depth -name '*[0-9]' -type d)

counting the total numbers of files and directories in a provided folder including subdirectories and their files

I want to count all the files and directories from a provided folder including files and directories in a subdirectory. I have written a script which will count accurately the number of files and directory but it does not handle the subdirectories any ideas ???
I want to do it without using FIND command
#!/bin/bash
givendir=$1
cd "$givendir" || exit
file=0
directories=0
for d in *;
do
if [ -d "$d" ]; then
directories=$((directories+1))
else
file=$((file+1))
fi
done
echo "Number of directories :" $directories
echo "Number of file Files :" $file
Use find:
echo "Number of directories: $(find "$1" -type d | wc -l)"
echo "Number of files/symlinks/sockets: $(find "$1" ! -type d | wc -l)"
Using plain shell and recursion:
#!/bin/bash
countdir() {
cd "$1"
dirs=1
files=0
for f in *
do
if [[ -d $f ]]
then
read subdirs subfiles <<< "$(countdir "$f")"
(( dirs += subdirs, files += subfiles ))
else
(( files++ ))
fi
done
echo "$dirs $files"
}
shopt -s dotglob nullglob
read dirs files <<< "$(countdir "$1")"
echo "There are $dirs dirs and $files files"
find "$1" -type f | wc -l will give you the files, find "$1" -type d | wc -l the directories
My quick-and-dirty shellscript would read
#!/bin/bash
test -d "$1" || exit
files=0
# Start with 1 to count the starting dir (as find does), else with 0
directories=1
function docount () {
for d in $1/*; do
if [ -d "$d" ]; then
directories=$((directories+1))
docount "$d";
else
files=$((files+1))
fi
done
}
docount "$1"
echo "Number of directories :" $directories
echo "Number of file Files :" $files
but mind it: On my build folder for a project, there were quite some differences:
find: 6430 dirs, 74377 non-dirs
my script: 6032 dirs, 71564 non-dirs
#thatotherguy's script: 6794 dirs, 76862 non-dirs
I assume that has to do with the legions of links, hidden files etc., but I am too lazy to investigate: find is the tool of choice.
Here are some one-line commands that work without find:
Number of directories: ls -Rl ./ | grep ":$" | wc -l
Number of files: ls -Rl ./ | grep "[0-9]:[0-9]" | wc -l
Explanation:
ls -Rl lists all files and directories recursively, one line each.
grep ":$" finds just the results whose last character is ':'. These are all of the directory names.
grep "[0-9]:[0-9]" matches on the HH:MM part of the timestamp. The timestamp only shows up on file, not directories. If your timestamp format is different then you will need to pick a different grep.
wc -l counts the number of lines that matched from the grep.

bash delete directories based on contents

Currently I have multiple directories
Directory1 Directory2 Directory3 Directory4
each of these directories contain files (the files are somewhat cryptic)
what i wish to do is scan files within the folders to see if certain files are present, if they are then leave that folder alone, if the certain files are not present then just delete the entire directory. here is what i mean:
im searching for the files that have the word .pass. in the filename.
Say Directory 4 has that file that im looking for
Direcotry4:
file1.temp.pass.exmpl
file1.temp.exmpl
file1.tmp
and the rest of the Directories do not have that specific file:
file.temp
file.exmp
file.tmp.other
so i would like to delete Directory1,2 and3 But only keep Directory 4...
So far i have come up with this code
(arr is a array of all the directory names)
for x in ${arr[#]}
do
find $x -type f ! -name "*pass*" -exec rd {} $x\;
done
another way i have thought of doing this is like this:
for x in ${arr[#]}
do
cd $x find . -type f ! -name "*Pass*" | xargs -i rd {} $x/
done
SO far these don't seem to work, and im scared that i might do something wrong and have all my files deleted.....(i have backed up)
is there any way that i can do this? remember i want Directory 4 to be unchanged, everything in it i want to keep
To see if your directory contains a pass file:
if [ "" = "$(find directory -iname '*pass*' -type f | head -n 1)" ]
then
echo notfound
else
echo found
fi
To do that in a loop:
for x in "${arr[#]}"
do
if [ "" = "$(find "$x" -iname '*pass*' -type f | head -n 1)" ]
then
rm -rf "$x"
fi
done
Try this:
# arr is a array of all the directory names
for x in ${arr[#]}
do
ret=$(find "$x" -type f -name "*pass*" -exec echo "0" \;)
# expect zero length $ret value to remove directory
if [ -z "$ret" ]; then
# remove dir
rm -rf "$x"
fi
done

Resources