How to move files en-masse while skipping a few files and directories - bash

I'm trying to write a shell script that moves all files except for the ones that end with .sh and .py. I also don't want to move directories.
This is what I've got so far:
cd FILES/user/folder
shopt -s extglob
mv !(*.sh|*.py) MoveFolder/ 2>/dev/null
shopt -u extglob
This moves all files except the ones that contain .sh or .py, but all directories are moved into MoveFolder as well.
I guess I could rename the folders, but other scripts already have those folders assigned for their work, so renaming might give me more trouble. I also could add the folder names but whenever someone else creates a folder, I would have to add its name to the script or it will be moved as well.
How can I improve this script to skip all folders?

Use find for this:
find -maxdepth 1 \! -type d \! -name "*.py" \! -name "*.sh" -exec mv -t MoveFolder {} +
What it does:
find: find things...
-maxdepth 1: that are in the current directory...
\! -type d: and that are not a directory...
\! -name "*.py: and whose name does not end with .py...
\! -name "*.sh: and whose name does not end with .sh...
-exec mv -t MoveFolder {} +: and move them to directory MoveFolder
The -exec flag is special: contrary to the the prior flags which were conditions, this one is an action. For each match, the + that ends the following command directs find to aggregate the file name at the end of the command, at the place marked with {}. When all the files are found, find executes the resulting command (i.e. mv -t MoveFolder file1 file2 ... fileN).

You'll have to check every element to see if it is a directory or not, as well as its extension:
for f in FILES/user/folder/*
do
extension="${f##*.}"
if [ ! -d "$f" ] && [[ ! "$extension" =~ ^(sh|py)$ ]]; then
mv "$f" MoveFolder
fi
done
Otherwise, you can also use find -type f and do some stuff with maxdepth and a regexp.
Regexp for the file name based on Check if a string matches a regex in Bash script, extension extracted through the solution to Extract filename and extension in Bash.

Related

Find Directory Name and Save as Variable

I'm trying to find a directory name and save it as a variable. In this case, this directory will always start with the character "2" and be the only such directory in its parent that starts with a "2". I'm trying to do the following but missing something:
#!/bin/bash
existing_dir=find $PARENT_DIR -maxdepth 1 -type d "2*"
rm -r $PARENT_DIR/$existing_dir
mkdir $PARENT_DIR/$((existing_dir+1))
#do stuff in new directory
In particular, I'm trying to grab that number that starts with the "2" (the directory name will always be only numerals), not the full path. Any help would be appreciated!
use basename to fetch only file name but no full path.
Then combine it with find when using -exec.
-exec basename {} \;
{} is the placeholder to pass a single file name to -exec called command, and \; is to finish -exec.
You had wrong usage of find. Here shows right style.
find $PARENT_DIR -maxdepth 1 -type d -name '2*' -exec basename {} \;
The whole command is generally equivalent to
for f in `find $PARENT_DIR -maxdepth 1 -type d -name '2*'`;
do basename ${f};
done
Sum up, you should correct it by using this.
existing_dir=`find . -maxdepth 1 -type d -name '2*' -exec basename {} \;`
Here is how to do it using bash only features:
#!/usr/bin/env bash
parent_dir='/define/some/path/here'
# Capture directories matching the 2* pattern into array
matching_dirs=("$parent_dir/2"*/)
# Regex match captures the numerical leaf directory name
# from the first mach in the matching_dirs array
[[ "${matching_dirs[0]}" =~ ([[:digit:]]+)/$ ]] || exit 1
existing_dir="${BASH_REMATCH[1]}"
new_dir="$((existing_dir + 1))"
# If could create new directory
if mkdir -p -- "${parent_dir:?}/${new_dir}"; then
# Deletes old directory recursively
# #see: https://github.com/koalaman/shellcheck/wiki/SC2115
rm -r -- "${parent_dir:?}/${existing_dir}"
fi

Recursively rename files to remove dots but leave extension unchanged

I've got a number of folders (80?) with files in them. Some of the filenames have multiple dots (file.name.ext). With the 'find' command being recursive, I'm able to rename the filenames within the folders from uppercase to lowercase:
find . -type f -execdir rename 'y/A-Z/a-z/' {} \;
Where 'find .' indicates to search the current folder. Where 'type f' searches only for files. Where 'execdir' executes the subsequent command on the output.
To do the same (uppercase to lowercase), but for the folders, both of these work:
rename 'y/A-Z/a-z/' *
rename 'y/A-Z/a-z/' ./*
To remove the dots from the foldername, this works:
find . -maxdepth 1 -execdir sed 's/[.]/_/g' {} \;
edit:(actually this is not working for me now)
...
What fails is when I try to recursively remove the dots:
find . -type f -execdir rename 's/\.(?=[^.]*\.)/_/g' '{}' \;
I get the error:
Can't rename ./filename.ext _/filename.ext: No such file or directory
I've also tried to add -printf "%f\n" to remove the leading ./ :
find . -type f -printf "%f\n" -execdir rename 's|[.]|_|g; s|_([^_]*)$|.$1|' {} \;
which outputs the filename followed by the same error
file.name.ext
Can't rename ./file.name.ext _/file.name.ext: No such file or directory
Those commands above were run from the parent folder 'above' the 80 folders that contain the files, with the idea of doing a dryrun (rename -nono) on all the files within the 80 folders at once.
From within one of those 80 folders I can remove the dots from the filename, leaving the dot in the extension unchanged, with:
rename 's/\.(?=[^.]*\.)/_/g'
But I don't want to have to go into each of those many folders to run the command. What will work recursively to delete all dots, leaving the extension dot alone?
I found the answer here:
Linux recursively replace periods for all directorys and all but last period for files with underscores
At first I didn't think it answered my specific question, but it actually does.
while IFS= read -rd '' entry; do
entry="${entry#./}" # strip ./
if [[ -d $entry ]]; then
rename 'y/A-Z/a-z/; s/ /_/g; s/\./_/g' "$entry"
else
rename 'y/A-Z/a-z/; s/ /_/g; s/\.(?=.*\.)/_/g' "$entry"
fi
done < <(find . -iname '*' -print0)
Thanks to anubhava .

How to delete all files or Sub-folders (both) in a folder except 2 folders with shell script

I would like to know how to deleted all the contents of a folder (it contains other folders and some files) except for 2 folders and its contents
The below command keeps the folder conf and removes all the other folders
find . ! -name 'conf' -type d -exec rm -rf {} +
I have tried to pipe it like below
find . -maxdepth 1 -type d ! -name 'conf' |find . -maxdepth 1 -type d ! -name 'foldername2'
but didnt work.
is it possible to do with a single command
You haven't specified which shell you're using, but if you're using bash then extended globs can help:
printf '%s\n' !(#(conf|foldername2)/)
If you're happy with the list of files and directories produced by that, then pass the same glob to rm -rf:
rm -rf !(#(conf|foldername2)/)
Inside a script, you may need to enable extglob using shopt -s extglob. Later, you can change -s to -u to unset the option.
If you're using a different shell, then you can add some more options to your find command:
find -maxdepth 1 ! -name 'conf' -a ! -name 'foldername2' -exec rm -rf {} +
Try it without the -exec part first to print the matches rather than deleting everything.
It may my little program utility can help you. I hope so.
First of all you should find the path of your files .sh
then you should find the main folder that contains those files .sh
then remove anything except those folders
I wrote drr for such a purpose that it can do such a task so easy
drr, stands for: remove or rename files based on regular expression in D language. So you must compile it before using.
See the screenshot:
Please be careful since this is not an appropriate tool for beginner.

finding and copying files using shell script (folder name and file name is same)

I have a directory that include several folders. I want to write a shell script to find a file in another directory that has the same name as the mentioned folders.
To clarify I have a directory that include test1 and test2 folders. I have another directory that have two files with the names of test1 and test2. My goal is to go to the directory that have folders and then get the folder names. Then by using folder name find the file that has the same name and copy it to that folder that has the same name.
I wrote the following script but it could not copy the file.
for d in /home/Documents/test/*/ ; do
find /home/Documents/binaries/ -name "$d" -type f -exec cp {} /home/Documents/test/$d \;
cd "$d"
done
$d will be set to a full path name, such as /home/Documents/test/test1/, but you only need test1 as the argument for the -name primary. You can use parameter expansion to strip the leading path from the value of $d, but it will take two steps.
for d in /home/Documents/test/*/ ; do
fname=${d##*/} # Strip /home/Documents/test/
fname=${fname%/} # Strip the trailing /
# Note that d is already the full directory name you want to use
# as the target file for `cp`
find /home/Documents/binaries/ -name "$fname" -type f -exec cp {} "$d" \;
done
The cd command doesn't seem to accomplish anything useful, since you are using absolute path names throughout.
Simpler, though, would be to change the working directory to /home/Documents/test first.
cd /home/Documents/test/
for d in */; do
find /home/Documents/binaries/ -name "$d" -type f -exec cp {} /home/Documents/test/"$d" \;
done

How to rename multiple directories in bash using a symbol pattern

I am very new to bash so please don't overcomplicate the answer!
I have roughly 200 sub-directories each named similarly to this. (I think they are sub directories. They live within another directory at least.)
XMMXCS J083454.8+553420.58
I need to bulk rename all of these directories and change the '+' in the directory name to '-'.
To change the names of my directory I have tried:
find . -depth -type d -name + -exec sh -c 'mv "${0}" "${0%/+}/-"' {} \;
and
find . -name + -type d -execdir mv {} - \
However I think this isn't working because + and - aren't letter characters.
How do I get around this?
Everything I have found online relates to renaming files as opposed to directories, and if anyone knows how to get round this without having to rename them all manually it would be very appreciated.
This previous question I have tried and the syntax doesn't work for me. The folders are all called the same thing after running.
Rename multiple directories matching pattern
Thanks
You can have a script like this.
#!/bin/bash
DIR='.' ## Change to the directory you want.
for SDIR in "$DIR"/*; do
[[ -d $SDIR ]] || continue ## Skip if it's not a directory
BASE=${SDIR##*/} ## Gets the base filename (removes directory part)
NEW_NAME=${BASE//+/-} ## Creates a new name based from $BASE with + chars changed to -
echo mv -- "$SDIR" "$DIR/$NEW_NAME" ## Rename. Remove echo if you think it works the right way already.
done
Then run bash script.sh.
Your original syntax was pretty close, try something like this
find -mindepth 1 -maxdepth 1 -type d -name '*+*' -exec bash -c 'mv "${0}" "${0//+/-}"' {} \;
Issues
-depth Performs a dfs traversal, but it seems like you only want directories one level deep
You need to match globs that contain +. So *+* and not just + (quoting is needed with globs so they get processed by find and not the shell)
With "${0%/+}/-" you seem to be mixing up a few syntaxes, ${0//SUBSTRING/TO_REPLACE} with replace all instances of SUBSTRING with TO_REPLACE

Resources