Find Directory Name and Save as Variable - bash

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

Related

How to get only the file name in the variable and not the complete path shell script?

mainFile=$(find /home/INVENT/custREAD -name '*.txt.gz' -type f -mmin 300)
I m using the above line in my shell script to fetch the file from the location /home/INVENT/custREAD for today's date and put it in the variable mainFile
But when I echo this variable, I see:
/home/INVENT/custREAD/filename.txt
But I want that only the file name to be in the variable,
Use finds printf directive.
mainFile=$(find "$dir" -name '*.txt.gz' -type f -mmin 300 -printf '%f\n')
Alternately, you can use shell parameter expansion to strip off the path:
mainFile=$(find "$dir" -name '*.txt.gz' -type f -mmin 300)
mainFile=${mainFile##*/} # remove the longest prefix ending with slash
You can add basename to your find call
mainFile=$(find /home/INVENT/custREAD -name '*.txt.gz' -type f -mmin 300 -exec basename {} \;)
Though I will warn that if you have more than one match your variable will contain multiple paths which will cause things to break (this is independent of my change to your find call).

how to use find result as part of regex in bash shell

I want to find out which directory doesn't have *.dsc file in it, so I had a try:
find . -type d -exec ls {}/*.dsc
the output is as below:
ls: connot access './abc/*.dsc': No such file or directory
I'm sure that there is a dsc file in abc/ directory.
Seems bash shell will treat "{}/*.dsc" as a string but not a regex, so I had another try:
find . -type d|xargs -I {} ls {}/*.dsc
but the result is the same.
How could I get the command work as I need?
Can you try this one out :
find . ! -iname "*.dsc" -type d
!: This is used to negate the match. It will list everything but files with .dsc extension.
-type d: This will fetch all the directories.
I wasn't able to use wildcards in find + ls, but the command below works.
find . -type d -not -path . | while read -r dir; do [ -f $dir/*\.dsc ] || echo $dir; done
It test separately whether a file *.dsc exists and echoes the directory otherwise.

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

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.

A script that iterates over all files in folder

There is a script on a server that I need to run over all the files in a folder. To run this script over one file I use this shell script:
for input in /home/arashsa/duo-bokmaal/Bokmaal/DUO_BM_28042.txt ; do
name=$(basename "$input")
/corpora/bokm/tools/The-Oslo-Bergen-Tagger/./tag-lbk.sh "$input" > "/home/arashsa/duo-bokmaal-obt/$name"
done
I'm terrible at writing shell scripts, and have not managed to found out how to iterate over files. What I want it is to make the script iterate over all files in a given folder that end with .txt and not those that end with _metadata.txt. So I'm thinking I would give it the folder path as argument, make it iterate over all the files in that folder, and run script on files ending with .txt and not _metadata.txt
Use find and the exec option.
$ find /path/to/dir -exec <command here> \;
Each file or directory can be obtained by using {}.
Example usage: $ find . -exec echo {} \;, this will echo each file name recursively or directory name in the current directory. You can use some other options to further specify the desired files and directories you wish to handle. I will briefly explain some of them. Note that the echo is redundant because the output of find will automatically print but I'll leave it there to illustrate the working of exec. This being said, following commands yield the same result: $ find . -exec echo {} \; and $ find .
maxdepth and mindepth
Specifying the maxdepth and mindepth allows you to go as deep down the directory structure as you like. Maxdepth determines how many times find will enter a directory and mindepth determines how many times a directory should be entered before selecting a file or dir.
Example usages:
(1) listing only elements from this dir, including . (= current dir).
(2) listing only elements from current dir excluding .
(3) listing elements from root dir and all dirs in this dir
(1)$ find . -maxdepth 1 -exec echo {} \;
(2)$ find . -mindepth 1 -maxdepth 1 -exec echo {} \;
# or, alternatively
(2)$ find . ! -path . -maxdepth 1 -exec echo {} \;
(3)$ find / -maxdepth 2 -exec echo {} \;
type
Specifying a type option allows you to filter files or directories only, example usage:
(1) list all files in this dir
(2) call shell script funtion func on every directory in the root dir.
(1)$ find . -maxdepth 1 -type f -exec echo {} \;
(2)$ find / -maxdepth 1 -type d -exec func {} \;
name & regex
The name option allows you to search for specific filenames, you can also look for files and dirs using a regex format.
Example usage: find all movies in a certain directory
$ find /path/to/dir -maxdepth 1 -regextype sed -regex ".*\.\(avi\|mp4\|mkv\)"
size
Another filter is the file size, any file or dir greater than this value will be returned. Example usage:
(1) find all empty files in current dir.
(2) find all non empty files in current dir.
(1)$ find . -maxdepth 1 -type f -size 0
(2)$ find . -maxdepth 1 -type f ! -size 0
Further examples
Move all files of this dir to a directory tmp present in .
$ find . -type f -maxdepth 1 -exec mv {} tmp \;
Convert all mkv files to mp4 files in a dir /path/to/dir and child directories
$ find /path/to/dir -maxdepth 2 -regextype sed -regex ".*\.mkv" -exec ffmpeg -i {} -o {}.mp4 \;
Convert all your jpeg files to png (don't do this, it will take very long to both find them and convert them).
$ find ~ -maxdepth 420 -regextype sed -regex '.*\.jpeg' -exec mogrify -format png {} \;
Note
The find command is a strong tool and it can prove to be fruitful to pipe the output to xargs. It's important to note that this method is superior to the following construction:
for file in $(ls)
do
some commands
done,
as the latter will handle files and directories containing spaces the wrong way.
In bash:
shopt -s extglob
for input in /dir/goes/here/*!(_metadata).txt
do
...
done

Loop thru directory and return directory name

Im trying to loop thru a directory (non recursive) and I only want to list the directory name, not the path.
find /dir/* -type d -prune -exec basename {} \;
This returns a list of directories in the dir and it works.
folder 1
this is folder2
And I want to loop thru these so I did:
for i in $(/dir/* -type d -prune -exec basename {} \;)
do
echo ${i}
done
But the for loop loops thru each word and not row. which results in this:
folder
1
this
is
folder2
I know there is a lot of threads on this but I haven't found anyone that works for me. Especally with spaces in the name.
Does anyone know how to solve this?
If you want to loop through directory names then you can use;
( cd /dir && for f in */; do echo "$f"; done )
In case you want to loop thru the find results only then better way to do that is:
while read -r f; do
echo "$f"
done < <(find /dir/ -type d -prune -exec basename '{}' \;)
This is preferred since it avoids spawning a subshell (though find -exec will create subshells).
Use a while loop instead:
find /dir/* -type d -prune -exec basename {} \; | while IFS= read -r line
do
echo "$line"
done
find /dir -maxdepth 1 -type d -printf %f

Resources