Parameters or arguments in bash - bash

I saw a question on stackflow about parsing arguments. I tried to write this, but it's not working and now it's getting on my nerves.
The usual way of running a script on the terminal is ./scriptname, but I later introduced the argument -d. So, if I put ./scriptname it will not run. If I put ./scriptname -d it will.
Now I want to put another argument for the path (where the files are moving, in this case "/home/elg19/documents") such that when I do not include the path, it won't run. But, if I put ./scriptname -d path I want to replace $To in the existing script with the command argument after -d.
#!/bin/bash
From="/home/mark/doc"
To=$2
if [ $1 = -d ]; then
cd "$From"
for i in pdf txt doc; do
find . -type f -name "*.${i}" -exec mv "{}" "$To" \;
done
fi

Your desired usage isn't completely clear, but it seems to be:
scriptname -d path
So, you can do it the extensible way, or the brute force way. Since you're changing directories willy-nilly, you also need to ensure that the paths are absolute, not relative.
Brute force
#!/bin/bash
From="/home/mark/doc"
if [ $# = 2 ] && [ "$1" = '-d' ] && [ -d $2 ]
then
case "$2" in
(/*) cd "$From" &&
for extn in pdf txt doc
do find . -type f -name "*.$extn" -exec mv {} "$To" \;
done;;
(*) echo "$0: path name must be absolute ($2 is not)" 1>&2; exit 1;;
esac
else
echo "Usage: $0 -d /absolute/dirname" 1>&2; exit 1
fi
Extensible
#!/bin/bash
From="/home/mark/doc"
To=""
usage()
{
echo "Usage: $(basename $0 .sh) -d /absolute/dirname" 1>&2
exit 1
}
while getopts d: opt
do
case "$opt" in
(d) if [ ! -d "$OPTARG" ]
then echo "$0: $OPTARG is not a directory" 1>&2; exit 1
else
case "$OPTARG" in
(/*) To="$OPTARG";;
(*) echo "$0: path name must be absolute ($2 is not)" 1>&2; exit 1;;
esac
fi;;
(*) usage;;
esac
done
shift $(($OPTIND - 1))
if [ $# != 0 ] || [ -z "$To" ]
then usage
fi
cd "$From" &&
for extn in pdf txt doc
do find . -type f -name "*.$extn" -exec mv {} "$To" \;
done
For example, it will be very easy to add a -f from option to deal with changing the source of the files.
Note that you could also use:
for extn in pdf txt doc
do find "$From" -type f -name "*.$extn" -exec mv {} "$To" \;
done
This would allow you to permit relative names for the 'from' and 'to' directories because it does not change directory.

I assume you want to do some input validation to your command line arguments. I guess the following would be somewhat useful:
#!/bin/bash
usage() {
echo "USAGE :"
echo "./move -d <to-directory>"
}
if [ $# -ne 2 ] ; then
usage
exit
fi
case $1 in
-d ) shift
To=$1
;;
* ) usage
exit
esac
From="/tmp/From/"
cd "$From"
for i in pdf txt doc; do
find . -type f -name "*.${i}" -exec mv "{}" "$To" \;
done
Moreover to debug your script, you may use the following command:
bash -x ./move.sh -d /tmp/To/
You may add more error checking (and informative echo's) for the following cases:
Source/destination directory does not exits
N files have been copied from the to
No files available at
You can take the type of files as arguments f.e. -t doc xls pdf

Related

Passing parameters to find in bash script

I use this bash command often
find ~ -type f -name \*.smt -exec grep something {} /dev/null \;
so I am trying to turn it into a simple bash script that I would invoke like this
findgrep ~ something --mtime -12 --name \*.smt
Thanks to this answer I managed to make it work like this:
if ! options=$(getopt -o abc: -l name:,blong,mtime: -- "$#")
then
exit 1
fi
eval "set -- $options"
while [ $# -gt 0 ]
do
case $1 in
-t|--mtime) mtime=${2} ; shift;;
-n|--name|--iname) name="$2" ; shift;;
(--) shift; break;;
(-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
(*) break;;
esac
shift
done
if [ $# -eq 2 ]
then
dir="$1"
str="$2"
elif [ $# -eq 1 ]
then
dir="."
str="$1"
else
echo "Need a search string"
exit
fi
echo "find $dir -type f -mtime $mtime -name $name -exec grep -iln \"$str\" {} /dev/null \;"
echo "find $dir -type f -mtime $mtime -name $name -exec grep -iln \"$str\" {} /dev/null \;" | bash
but the last line - echo'ing a command into bash - seems outright barbaric, but it works.
Is there a better way to do that? somehow trying to execute the find command directly gives no output, while running the one echo'ed out in bash works ok.
ame $name -e
It's still not quoted. Check your script with shellcheck.
find "$dir" -type f -mtype "$mtime" -name "$name" -exec grep -iln "$str" {} ';'
You might want to take a few steps back and do some research about quoting and expansions in shel, find and glob. find program expects literal glob pattern, and unquoted variable expansions undergo filename expansion, changing *.smt into the list of words representing filenames, while find wants the pattern not the result of expansions.
I can throw: man find, man 7 glob, https://www.gnu.org/software/bash/manual/html_node/Quoting.html https://mywiki.wooledge.org/BashFAQ/050
https://mywiki.wooledge.org/BashGuide/Parameters#Parameter_Expansion
Before you start deciding how to pass variable number of arguments to find, I encourage to research Bash arrays. I would do:
#!/bin/bash
fatal() {
echo "$0: ERROR: $*" >&2
exit 1
}
args=$(getopt -o abc: -l name:,iname:,mtime: -- "$#") || exit 1
eval "set -- $args"
findargs=() # bash array
while (($#)); do
case $1 in
-t|--mtime) findargs+=(-mtime "$2"); shift; ;;
-n|--name) findargs+=(-name "$2"); shift; ;;
--iname) findargs+=(-iname "$2"); shift; ;;
--) shift; break; ;;
-*) fatal "unrecognized option $1"; ;;
*) break; ;;
esac
shift
done
if (($# == 2)); then
dir="$1"
str="$2"
elif (($# == 1)); then
dir="."
str="$1"
else
fatal "Need a search string"
fi
set -x
find "$dir" -type f "${findargs[#]}" -exec grep -iln "$str" /dev/null {} +

Find and copy files to directorywith getopts

I have a bash script that I need to pass in the files to be copied (*.cpp) and the directory to copy to (cfiles/backup). The problem is it only copies the first file instead of all the files in the directory.
#!/bin/bash
while getopts "ab:" input; do
case $input in
a)
#an option
;;
b)
# Get the wild card and destination passed in
# wildcard=$OPTARG
dest="${#: -1}"
#Make the directory if it doesn't exit
mkdir -p $dest 2>1
find . -name "$OPTARG" -type f -exec cp {} $dest \; 2>1
printf 'string = %b| destination = %b\n' $OPTARG $dest
;;
?)
echo "Error! Invalid option provided" >&2
exit 1
;;
:)
echo "Option -$OPTARG missing parameter!" >&2
;;
esac
done
The problem is it only ever copies 1 file any insight will be appreciated!
You need to add a -r at the end of your cp command. This performs a recursive file copy on the directory.
find . -name "$OPTARG" -type f -exec cp -r {} $dest \; 2>1

Bash Find Function Ubuntu - Find in directory tree, files that have the same name as their directory

I want to find and print files in directory tree, that have the sname name as theirs dirs.
This is my code so far:
#!bin/bash
if [ $# -eq 0 ]
then
echo "No args"
fi
if [[ -d $1 ]] #if its dir
then
find $1 -type f | (while read var1 #for every regular file in dir tree
do
if [[ -f $var1 ]]
then
echo $var1 #full path
# I dont know how to get the dir name
echo $(basename $var1) #file name
echo
#then compare it and print full path
fi
done)
fi
I want to do this using FIND function in bash linux. Thanks
You can use this script with find:
while IFS= read -rd '' f; do
d="${f%/*}"
[[ ${d##*/} == ${f##*/} ]] && echo "$f"
done < <(find . -type f -print0)

How to refactor a find | xargs one liner to a human readable code

I've written an OCR wrapper batch & service script for tesseract and abbyyocr11 found here: https://github.com/deajan/pmOCR
The main function is a find command that passes it's arguments to xargs with -print0 in order to deal with special filenmames.
The find command became more and more complex and ended up as a VERY long one liner that becomes difficult to maintain:
find "$DIRECTORY_TO_PROCESS" -type f -iregex ".*\.$FILES_TO_PROCES" ! -name "$find_excludes" -print0 | xargs -0 -I {} bash -c 'export file="{}"; function proceed { eval "\"'"$OCR_ENGINE_EXEC"'\" '"$OCR_ENGINE_INPUT_ARG"' \"$file\" '"$OCR_ENGINE_ARGS"' '"$OCR_ENGINE_OUTPUT_ARG"' \"${file%.*}'"$FILENAME_ADDITION""$FILENAME_SUFFIX$FILE_EXTENSION"'\" && if [ '"$_BATCH_RUN"' -eq 1 ] && [ '"$_SILENT"' -ne 1 ];then echo \"Processed $file\"; fi && echo -e \"$(date) - Processed $file\" >> '"$LOG_FILE"' && if [ '"$DELETE_ORIGINAL"' == \"yes\" ]; then rm -f \"$file\"; fi"; }; if [ "'$CHECK_PDF'" == "yes" ]; then if ! pdffonts "$file" 2>&1 | grep "yes" > /dev/null; then proceed; else echo "$(date) - Skipping file $file already containing text." >> '"$LOG_FILE"'; fi; else proceed; fi'
Is there a nicer way to pass the find results to a human readable function (without impacting too much speed) ?
Thanks.
Don't use bash -c. You are already committed to starting a new bash process for each file from the find command, so just save the code to a file and run that with
find "$DIRECTORY_TO_PROCESS" -type f -iregex ".*\.$FILES_TO_PROCES" \
! -name "$find_excludes" -print0 |
xargs -0 -I {} bash script.bash {}
You can replace find altogether. It's easier in bash 4 (which I'll show here), but doable in bash 3.
proceed () {
...
}
shopt -s globstar
extensions=(pdf tif tiff jpg jpeg bmp pcx dcx)
for ext in "${extensions[#]}"; do
for file in /some/path/**/*."$ext"; do
[[ ! -f $file || $file = *_ocr.pdf ]] && continue
# Rest of script here
done
done
Prior to bash 4, you can write your own recursive function to descend through a directory hierarchy.
descend () {
for fd in "$1"/*; do
if [[ -d $fd ]]; then
descend "$fd"
elif [[ ! -f $fd || $fd != *."$ext" || $fd = *_ocr.pdf ]]; then
continue
else
# Rest of script here
fi
done
}
for ext in "${extensions[#]}"; do
descend /some/path "$ext"
done
OK, create the script, then run find.
#!/bin/bash
trap cleanup EXIT
cleanup() { rm "$script"; }
script=$(mktemp)
cat <<'END' > "$script"
########################################################################
file="$1"
function proceed {
"$OCR_ENGINE_EXEC" "$OCR_ENGINE_INPUT_ARG" "$file" "$OCR_ENGINE_ARGS" "$OCR_ENGINE_OUTPUT_ARG" "${file%.*}$FILENAME_ADDITION$FILENAME_SUFFIX$FILE_EXTENSION"
if [ "$_BATCH_RUN" -eq 1 ] && [ "$_SILENT" -ne 1 ]; then
echo "Processed $file"
fi
echo -e "$(date) - Processed $file" >> "$LOG_FILE"
if [ "$DELETE_ORIGINAL" == "yes" ]; then
rm -f "$file"
fi
}
if [ "$CHECK_PDF" == "yes" ]; then
if ! pdffonts "$file" 2>&1 | grep "yes" > /dev/null; then
proceed
else
echo "$(date) - Skipping file $file already containing text." >> '"$LOG_FILE"';
fi
else
proceed
fi
########################################################################
END
find "$DIRECTORY_TO_PROCESS" -type f \
-iregex ".*\.$FILES_TO_PROCES" \
! -name "$find_excludes" \
-exec bash "$script" '{}' \;
The 'END' of the heredoc is quoted, so the variables are not expanded until the script is actually executed.
I finished using a while loop with a substituted find command, ie:
while IFS= read -r -d $'\0' file; do
if ! lsof -f -- "$file" > /dev/null 2>&1; then
if [ "$_BATCH_RUN" == true ]; then
Logger "Preparing to process [$file]." "NOTICE"
fi
OCR "$file" "$fileExtension" "$ocrEngineArgs" "$csvHack"
else
if [ "$_BATCH_RUN" == true ]; then
Logger "Cannot process file [$file] currently in use." "ALWAYS"
else
Logger "Deferring file [$file] currently being written to." "ALWAYS"
kill -USR1 $SCRIPT_PID
fi
fi
done < <(find "$directoryToProcess" -type f -iregex ".*\.$FILES_TO_PROCES" ! -name "$findExcludes" -and ! -wholename "$moveSuccessExclude" -and ! -wholename "$moveFailureExclude" -and ! -name "$failedFindExcludes" -print0)
The while loop reads every file from the find command in file variable.
Using -d $'\0' in while and -print0 in find command helps dealing with special filenames.

Bash Script - Find File by User Input

I'm trying to compile a very simple bash script that will do the following actions (the script I have so far doesn't seem to function at all so I won't waste time putting this up for you to look at)
I need it to find files by their names. I need the script to take the user input and search the .waste directory for a match, should the folder be empty i'd need to echo out "No match was found because the folder is empty!", and just normally failing to find a match a simple "No match found."
I have defined: target=/home/user/bin/.waste
You can use the built in find command to do this
find /path/to/your/.waste -name 'filename.*' -print
Alternatively, you can set this as a function in your .bash_profile
searchwaste() {
find /path/to/your/.waste -name "$1" -print
}
Note that there are quotes around the $1. This will allow you to do file globbing.
searchwaste "*.txt"
The above command would search your .waste directory for any .txt files
Here you go, pretty straightforward script:
#!/usr/bin/env bash
target=/home/user/bin/.waste
if [ ! "$(ls -A $target)" ]; then
echo -e "Directory $target is empty"
exit 0
fi
found=0
while read line; do
found=$[found+1]
echo -e "Found: $line"
done < <(find "$target" -iname "*$1*" )
if [[ "$found" == "0" ]]; then
echo -e "No match for '$1'"
else
echo -e "Total: $found elements"
fi
Btw. in *nix world there are not folders, but there are directories :)
This is a solution.
#!/bin/bash
target="/home/user/bin/.waste"
read name
output=$( find "$target" -name "$name" 2> /dev/null )
if [[ -n "$output" ]]; then
echo "$output"
else
echo "No match found"
fi

Resources