Asterisk in for loop not working as expected? - linux

I don't understand why this is not working. I am trying to loop through files and folders and delete some of them depending on the name. In the example below delete all folders except the ones in the if statement.
Here's the code:
#!/bin/bash
workdir=/var/www/
for dir in $workdir/custom/*; do
if ! [ "$dir" == "$workdir/custom/somefolder" ]; then
rm -rf $dir
echo "remove $dir $?"
fi
echo "$dir"
done
The problem is that there are several files and folders in /custom/ but echo "$dir" outputs /var/www/custom/* once
instead of running through every file and folder in that directory. I know this means that * didn't match anything, but this is impossible.
The folder exists, has several files and folders in it and the path is correct, also the user has all needed permissions to rm files, I checked that twice.
What am I missing?

find /var/www/custom/ -name "*" -type d -mindepth 1 -exec rm -rf {} \;
this will remove all the directories inside your custom directory. If this is what is required.

Related

Unix shell loop to check if a directory exists inside multiple directories

I have folder structure like this:
/home/
/folder1/
/backup/
/folder2/
/backup/
/folder3/
/folder4/
/backup/
/folder5/
(As you can see, no all directories "folder" have a directory "backup")
I need to check if the directory "backup" exists in the "folder"s and delete it.
I am using this command:
for d in /home/* ;
do [ -d "$d/backup" ]
&& echo "/backup exists in $d"
&& rm -rf "$d/backup"
&& echo "/backup deleted in $d" ;
done
But it is not working. Please help.
find . -type d -name "backup" -delete -print
Obviously, all content under backup directories will be lost.
This will recurse down into your directories. If you need to limit it to only the first level, you can do:
find . -maxdepth 1 -type d -name "backup" -delete -print
Both commands will print the deleted directories. No output == no directory found, nothing done.
Lastly, you want to avoid looping on files or directory names like you attempted, since you might have files or directories with spaces in their names. A complete discussion and solutions are available here: https://mywiki.wooledge.org/BashFAQ/001

Check if files exist in multiple directories using wildcards

I have ~10,000 directories. Most of them have a similarly named text file.
I would like to take these .txt files and copy them to a folder in the main directory, ALL_RESULTS. How can i accomplish this? What I have below
for d in *_directories/; do
#go into directory
cd "$d"
#check if file exists using wildcard, then copy it into ALL_RESULTS and print the name of
#directory out
if ls *SCZ_PGC3_GWAS.sumstats.gz*.txt 1> /dev/null 2>&1; then
cp *SCZ_PGC3_GWAS.sumstats.gz*.txt ../ALL_RESULTS && echo "$d"
#if file does not exist, print the name of the directory we're
#in
else
echo "$d"
echo "files do not exist"
cd ..
fi
done
I keep getting errors saying the directories themselves don't exist. What am I doing wrong?
All relative paths are interpreted relative to the directory you are in (the "current working directory"). So, imagine, you cd into the first directory. So now you are in that directory. Then you loop executes, and you try to cd into the second directory. But that directory is no longer then, you need to go "up" and then cd into the directory. That is the reason the directory does not exists - you have to go "up" a directory for each directory you cd into.
So you need to cd .. on the end of your loop to go back to the directory you started from.
I have ~10,000 directories. ... I would like to take these .txt files and move them to a folder in the main directory, ALL_RESULTS
If you don't need to output anything, just use find for that with a proper regex. Doing ls and cd and a loop will be very slow. Something along:
find . -maxdepth 2 -type f -regex '\./.*_directories/.*SCZ_PGC3_GWAS.sumstats.gz.*\.txt' -exec cp {} ALL_RESULTS \;
You can also add -v to cp to see what it copies.
You miss
shopt -s nullglob
and don't parse ls output :
#!/bin/bash
shopt -s nullglob
for d in *_directories/; do
# check if file exists using wildcard, then copy it into ALL_RESULTS and print
# the name of directory
files=$( $d/*SCZ_PGC3_GWAS.sumstats.gz*.txt )
if [[ ${files[#]} ]]; then
cp "${files[#]}" ALL_RESULTS && echo "$d"
#if file does not exist, print the name of the directory we're
#in
else
echo "$d"
echo "files do not exist"
fi
done

delete specific files and retain some

so I have this directory that includes these .js and .yml files and one folder named as config
pogi#gwapo-pah:~$ ls
index.cat.js
index.bird.js
index.dog.js
index.monkey.js
function.yml
config
I would like to execute a one-liner bash command that would perform these
find if "index.dog.js" exists, and if none then exit
find if "index.dog.js" exists, and if present then remove only the
other *.js files and retain index.dog.js, function.yml and the folder config
if command is success then the files from folder shold look like this:
index.dog.js
function.yml
config
these is so far I tried however I'm not able to continue the missing logic
if [ -f index.dog.js ] ; then echo 'exists' ; fi
shopt -s extglob
[[ -f index.dog.js ]] && rm !(index.dog).js
Another way using find command:
[ -f "index.dog.js" ] && find . -maxdepth 1 -name \*.js -not -name index.dog.js -delete
find command search in current directory any file with js extension but index.dog.js and remove it.
replace . with folder name if you are not inside directory where are file.
Test if "index.dog.js" exists, if it does, use find to yield all *.js files (but not index.dog.js), and delete them.
EDIT As John Kugelman correctly advises, best to avoid ls due to possible inconsistencies with it.
[ -f "index.dog.js" ] && \
find . -type f -not -name "index.dog.js" -name \*.js -exec rm {} +
test -f index.dog.js && find . -name \*.js -not -name index.dog.js -exec rm {} +
Explanation:
test is a way to do if without all the extra syntax, if you don't need the else.
&& is the "short circuit" (exit) you want if there is no dog file.
find looks for files using multiple criteria. In this case files whose name match *.js but are not the dog file.
find can then execute a command against the found files. The {} is a stand-in for the found files. The + means put all the filenames on one rm command, rather than running one command per file.

using IF to see a directory exists if not do something

I am trying to move the directories from $DIR1 to $DIR2 if $DIR2 does not have the same directory name
if [[ ! $(ls -d /$DIR2/* | grep test) ]] is what I currently have.
then
mv $DIR1/test* /$DIR2
fi
first it gives
ls: cannot access //data/lims/PROCESSING/*: No such file or directory
when $DIR2 is empty
however, it still works.
secondly
when i run the shell script twice.
it doesn't let me move the directories with the similar name.
for example
in $DIR1 i have test-1 test-2 test-3
when it runs for the first time all three directories moves to $DIR2
after that i do mkdir test-4 at $DIR1 and run the script again..
it does not let me move the test-4 because my loop thinks that test-4 is already there since I am grabbing all test
how can I go around and move test-4 ?
Firstly, you can check whether or not a directory exists using bash's built in 'True if directory exists' expression:
test="/some/path/maybe"
if [ -d "$test" ]; then
echo "$test is a directory"
fi
However, you want to test if something is not a directory. You've shown in your code that you already know how to negate the expression:
test="/some/path/maybe"
if [ ! -d "$test" ]; then
echo "$test is NOT a directory"
fi
You also seem to be using ls to get a list of files. Perhaps you want to loop over them and do something if the files are not a directory?
dir="/some/path/maybe"
for test in $(ls $dir);
do
if [ ! -d $test ]; then
echo "$test is NOT a directory."
fi
done
A good place to look for bash stuff like this is Machtelt Garrels' guide. His page on the various expressions you can use in if statements helped me a lot.
Moving directories from a source to a destination if they don't already exist in the destination:
For the sake of readability I'm going to refer to your DIR1 and DIR2 as src and dest. First, let's declare them:
src="/place/dir1/"
dest="/place/dir2/"
Note the trailing slashes. We'll append the names of folders to these paths so the trailing slashes make that simpler. You also seem to be limiting the directories you want to move by whether or not they have the word test in their name:
filter="test"
So, let's first loop through the directories in source that pass the filter; if they don't exist in dest let's move them there:
for dir in $(ls -d $src | grep $filter); do
if [ ! -d "$dest$dir" ]; then
mv "$src$dir" "$dest"
fi
done
I hope that solves your issue. But be warned, #gniourf_gniourf posted a link in the comments that should be heeded!
If you need to mv some directories to another according to some pattern, than you can use find:
find . -type d -name "test*" -exec mv -t /tmp/target {} +
Details:
-type d - will search only for directories
-name "" - set search pattern
-exec - do something with find results
-t, --target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY
There are many examples of exec or xargs usage.
And if you do not want to overwrite files, than add -n option to mv command:
find . -type d -name "test*" -exec mv -n -t /tmp/target {} +
-n, --no-clobber do not overwrite an existing file

moving files to different directories

I'm trying to move media and other files which are in a specified directory to another directory and create another one if it does not exits (where the files will go), and create a directory the remaining files with different extensions will go. My first problem is that my script is not making a new directory and it is not moving the files to other directories and what code can I use to move files with different extensions to one directory?
This is what i have had so far, correct me where I'm wrong and help modify my script:
#!/bin/bash
From=/home/katy/doc
To=/home/katy/mo #directory where the media files will go
WA=/home/katy/do # directory where the other files will go
if [ ! -d "$To" ]; then
mkdir -p "$To"
fi
cd $From
find path -type f -name"*.mp4" -exec mv {} $To \;
I'd solve it somewhat like this:
#!/bin/bash
From=/home/katy/doc
To=/home/katy/mo # directory where the media files will go
WA=/home/katy/do # directory where the other files will go
cd "$From"
find . -type f \
| while read file; do
dir="$(dirname "$file")"
base="$(basename "$file")"
if [[ "$file" =~ \.mp4$ ]]; then
target="$To"
else
target="$WA"
fi
mkdir -p "$target/$dir"
mv -i "$file" "$target/$dir/$base"
done
Notes:
mkdir -p will not complain if the directory already exists, so there's no need to check for that.
Put double quotes around all filenames in case they contain spaces.
By piping the output of find into a while loop, you also avoid getting bitten by spaces, because read will read until a newline.
You can modify the regex according to taste, e.g. \.(mp3|mp4|wma|ogg)$.
In case you didn't know, $(...) will run the given command and stick its output back in the place of the $(...) (called command substitution). It is almost the same as `...` but slightly better (details).
In order to test it, put echo in front of mv. (Note that quotes will disappear in the output.)
cd $From
find . -type f -name "*.mp4" -exec mv {} $To \;
^^^
or
find $From -type f -name "*.mp4" -exec mv {} $To \;
^^^^^
cd $From
mv *.mp4 $To;
mv * $WA;

Resources