Code in bashrc doesn't work - bash

The code below doesn't work in bashrc but works in terminal with other arguments null.
search () {
find $1 -type f | egrep '(.$2|.$3|.$4|.$5|.$6|.$7|.$8|.$9|.$10)'
}

Write this:
search() {
find "$1" -type f \( -true \
-o -name "*$2*" \
-o -name "*$3*" \
-o -name "*$4*" \
-o -name "*$5*" \
-o -name "*$6*" \
-o -name "*$7*" \
-o -name "*$8*" \
-o -name "*$9*" \
-o -name "*$10*" \
\)
}
As #chepner points out, the single quotes prevent the parameters from expanding. Use double quotes.
The egrep will create a line-based match result, which is less precise than the above. It's also slower.
If the above statements are not exactly what you need, keep in mind GNU find has regular expression predicates in addition to -name's pattern matching. There's no need to pipe to grep. You can expand the above function to take an unlimited number of arguments by constructing the arguments to find, such as in this answer.

I didn't know that the egrep get the literal text $2 instead of argument. I solved with this code:
search-type () {
case "$#" in
1) echo "Missing arguments";;
2) find $1 -type f | egrep '(.'$2')';;
3) find $1 -type f | egrep '(.'$2'|.'$3')';;
4) find $1 -type f | egrep '(.'$2'|.'$3'|.'$4')';;
5) find $1 -type f | egrep '(.'$2'|.'$3'|.'$4'|.'$5')';;
6) find $1 -type f | egrep '(.'$2'|.'$3'|.'$4'|.'$5'|.'$6')';;
7) find $1 -type f | egrep '(.'$2'|.'$3'|.'$4'|.'$5'|.'$6'|.'$7')';;
8) find $1 -type f | egrep '(.'$2'|.'$3'|.'$4'|.'$5'|.'$6'|.'$7'|.'$8')';;
9) find $1 -type f | egrep '(.'$2'|.'$3'|.'$4'|.'$5'|.'$6'|.'$7'|.'$8'|.'$9')';;
10) find $1 -type f | egrep '(.'$2'|.'$3'|.'$4'|.'$5'|.'$6'|.'$7'|.'$8'|.'$9'|.'$10')';;
11) echo "Many arguments";;
esac;
}
The #kojiro code doesn't work.
Is it possible to simplify this code with regex?
Thank you guys!

I change the code for the something more simple and clear; and works with any quantity of parameters.
search-type() {
# Flags
flag=0
fld=1
for x in "$#"
do
# The first parameter is the directory; ignored!
if [ $fld = 1 ]; then
fld=0
else
# Verify if have more than one file
if [ $flag = 0 ]; then
cmd='-name '$x
flag=1
else
cmd+=' -o -name '$x
fi
fi
done
find $1 -type f $cmd;
}

Related

How do I use parens '()' in a find command when building options from array?

I have a function that looks like this. I have stripped error handling, and the commands outside the function are to make sure I have something to look for in the example.
#!/bin/bash
findfiles() {
local path=$1
local mtime=$2
local prunedirs=$3
local -a fopts
fopts+=("$path")
[[ -n $prunedirs ]] && {
fopts+=('-type' 'd')
fopts+=('(' '-path')
fopts+=("${prunedirs// / -o -path }")
fopts+=(')' '-prune' '-o')
}
fopts+=('-type' 'f')
fopts+=('-writable')
fopts+=('-mtime' "+$mtime")
[[ -n $prunedirs ]] && fopts+=('-print')
echo "find ${fopts[*]}"
find "${fopts[#]}"
}
mkdir -p dir1/{dir2,dir3}
touch dir1/5daysago.txt -mt "$(date -d 'now - 5 days' +%Y%m%d%H%M)"
touch dir1/dir2/6daysago.txt -mt "$(date -d 'now - 6 days' +%Y%m%d%H%M)"
touch dir1/dir3/10daysago.txt -mt "$(date -d 'now - 10 days' +%Y%m%d%H%M)"
echo '---------------------------------------------'
findfiles dir1 4
echo '---------------------------------------------'
findfiles dir1 4 'dir1/dir2'
echo '---------------------------------------------'
findfiles dir1 4 "dir1/dir2 dir1/dir3"
This outputs the following:
---------------------------------------------
find dir1 -type f -writable -mtime +4
dir1/dir2/6daysago.txt
dir1/dir3/10daysago.txt
dir1/5daysago.txt
---------------------------------------------
find dir1 -type d ( -path dir1/dir2 ) -prune -o -type f -writable -mtime +4 -print
dir1/dir3/10daysago.txt
dir1/5daysago.txt
---------------------------------------------
find dir1 -type d ( -path dir1/dir2 -o -path dir1/dir3 ) -prune -o -type f -writable -mtime +4 -print
dir1/dir2/6daysago.txt
dir1/dir3/10daysago.txt
dir1/5daysago.txt
Notice that the third attempt does not prune the directories. If I copy and paste the find (escaping the parens) it works correctly.
$ find dir1 -type d \( -path dir1/dir2 -o -path dir1/dir3 \) -prune -o -type f -writable -mtime +4 -print
dir1/5daysago.txt
What am I doing wrong?
You need to add -o and the -path primary as separate array elements. Each directory to prune should be passed as a separate argument, not a single space-separated string.
findfiles() {
local path=$1
local mtime=$2
shift 2
n=$# # Remember for later
local -a fopts
fopts+=("$path")
if (( $# > 0 )); then
fopts+=(-type d '(')
while (( $# > 1 )); do
fopts+=(-path "$1" -o)
shift
done
fopts+=(-path $1 ')' -prune -o)
fi
fopts+=('-type' 'f')
fopts+=('-writable')
fopts+=('-mtime' "+$mtime")
# Now it's later
((n > 0)) && fopts+=('-print')
echo "find ${fopts[*]}"
find "${fopts[#]}"
}
findfiles dir1 4 "dir1/dir2" "dir1/dir3"
Change echo "find ${fopts[*]}" to declare -p fopts to unambiguously print the options. Doing so will show that the -o -path part is being added as a single word:
$ declare -p fopts
declare -a fopts=(
[0]="dir1" [1]="-type" [2]="d" [3]="(" [4]="-path"
[5]="dir1/dir2 -o -path dir1/dir3" [6]=")" [7]="-prune" [8]="-o" [9]="-type" [10]="f"
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[11]="-writable" [12]="-mtime" [13]="+4" [14]="-print"
)
To fix it you'll want to add each directory to prune to the array individually, something like:
local prunedirs=("${#:3}")
...
fopts+=(-type d '(' -false)
for dir in "${prunedirs[#]}"; do
fopts+=(-o -path "$dir")
done
fopts+=(')' -prune -o)
I've switched prunedirs to an array so it can handle directory names with whitespace.
It starts with an initial -false check so there's no need to check if prunedirs is empty. If it's empty the whole thing is still added but since it just says -type d '(' -false ')' -prune -o it's a no-op.
Also, notice you don't have to quote every single argument. It's fine to write -type d and such unquoted, the same as you would if you typed them at the command line. Only '(' and ')' need single quotes.

Passing a date/time value to touch command within bash function

I am trying to write a bash function (Mac OS X) which searches for a specific range of files between two input date/times. Most variations I have tried for evaluating the two inputs $1 and $2 fail. Hardcoding the times works fine (as per usage line below) i.e. the search syntax is fine. Grateful for pointers where I'm going wrong on passing the two inputs to the touch commands.
function ffiles_search1 () {
echo "usage start 201911270000 end 201912102359 "
touch -t $(eval echo "$1") /tmp/lower-date && touch -t $(eval echo "$2") /tmp/upper-date && find . -path "./Library" -prune -o -type f -a -newer /tmp/lower-date -a ! -newer /tmp/upper-date -a -size +32k -a ! -size +1024k -print0 | xargs -0 ls -ld | egrep -iv "|ppt|doc"
}
Corrected code, invoking as
ffiles_search 201911270000 201912102359
on the function
function ffiles_search () {
echo "usage start 201911270000 end 201912102359 "
touch -t "$1" /tmp/lower-date &&
touch -t "$2" /tmp/upper-date &&
find . -path "./Library" -prune -o -type f -a -newer /tmp/lower-date \
-a ! -newer /tmp/upper-date -a -size +32k -a -size -1024k -print0 |
xargs -0 ls -ld |
egrep -iv -f $HOME/Scripts/egrep_exclusions/time_search.txt
}

Bash Script interactive mv issues

I'm working on a bash script to help organize files and I want to use mv -i to make sure I don't write over something important.
The script is working right now except for the -i for the mv.
It shows (y/n [n]) not overwritten part, but then goes and and doesn't allow me to interact with it.
createList()
{
ls *.epub | sed 's/-.*//' |uniq >> list.txt
ls *.mobi | sed 's/-.*//' |uniq >> list2.txt
}
atag()
{
find /Users/j/Desktop/Source -maxdepth 1 -iname "*.epub" -type f -print0 | xargs -0 -I '{}' tag -a Purple {}
find /Users/j/Desktop/Source -maxdepth 1 -iname "*.mobi" -type f -print0 | xargs -0 -I '{}' tag -a Purple {}
}
moveEpub()
{
while read -r line; do
if [ -d "/Users/j/Desktop/Dest/$line" ]; then
if [ -d "/Users/j/Desktop/Dest/$line/EPUB" ]; then
find /Users/j/Desktop/Source/ -maxdepth 1 -iname "*$line*" -and ! -iname ".*$line*" -type f -print0 | xargs -0 -I '{}' mv -i {} /Users/j/Desktop/Dest/"$line"/EPUB/
else
mkdir "/Users/j/Desktop/Dest/$line/EPUB"
find /Users/j/Desktop/Source/ -maxdepth 1 -iname "*$line*" -and ! -iname ".*$line*" -type f -print0 | xargs -0 -I '{}' mv -i {} /Users/j/Desktop/Dest/"$line"/EPUB/
fi
fi
done < "list.txt"
}
moveMobi()
{
while read -r line; do
if [ -d "/Users/j/Desktop/Dest/$line" ]; then
if [ -d "/Users/j/Desktop/Dest/$line/MOBI" ]; then
find /Users/j/Desktop/Source/ -maxdepth 1 -iname "*$line*" -and ! -iname ".*$line*" -type f -print0 | xargs -0 -I '{}' mv -i {} /Users/j/Desktop/Dest/"$line"/MOBI/
else
mkdir "/Users/j/Desktop/Dest/$line/MOBI"
find /Users/j/Desktop/Source/ -maxdepth 1 -iname "*$line*" -and ! -iname ".*$line*" -type f -print0 | xargs -0 -I '{}' mv --interactive {} /Users/j/Desktop/Dest/"$line"/MOBI/
fi
fi
done < "list2.txt"
}
clear
createList
atag
moveEpub
moveMobi
rm list.txt
rm list2.txt
If you want mv -i to interact with the terminal, that means its stdin needs to be attached to that terminal. There are several places, here, where you're overriding stdin.
For instance:
# THIS LOOP OVERRIDES STDIN
while read -r line
...
done <list.txt
...redirects stdin for the entire duration of the loop, so instead of reading from the user, mv reads from list.txt. To change this, use a different file descriptor:
# This loop uses FD 3 for stdin
while read -r line <&3
...
done 3<list.txt
Another place is in calling xargs. Instead of:
# Overrides stdin for xargs and mv to contain output from find
find ... -print0 | xargs -0 -I '{}' mv -i '{}' "$dest"
...use:
# directly executes mv from find, stdin not modified
find ... -exec mv -i '{}' "$dest" ';'
That said, I would suggest ditching list.txt and list2.txt altogether; you simply don't need them; for that matter, you don't need find either.
dest=/Users/j/Desktop/Dest
source=/Users/j/Desktop/Source
moveEpub() {
local -A finished=( ) # WARNING: This requires bash 4.0 or newer.
for name in *.epub; do
prefix=${name%%-*} # remove everything past the first dash
[[ ${finished[$prefix]} ]] && continue # skip if already done with this prefix
finished[$prefix]=1 # set flag to skip other files w/ this prefix
[[ -d $dest/$prefix ]] || continue # skip if no directory exists for this prefix
mkdir -p "$dest/$prefix/EPUB" # create destination if not existing
mv -i "$source"/*"$prefix"* "$dest/$prefix/EPUB"
done
}
You can use built in find action -exec instead of piping to xargs :
find /Users/j/Desktop/Source/ -maxdepth 1 \
-iname "*$line*" -and ! -iname ".*$line*" -type f \
-exec mv -i {} /Users/j/Desktop/Dest/"$line"/EPUB/ \;

Using awk to print ALL spaces within filenames which have a varied number of spaces

I'm executing the following using bash and awk to get the potentially space-full filename, colon, file size. (Column 5 contains the space delimited size, and 9 to EOL the file name):
src="Desktop"
echo "Constructing $src files list. `date`"
cat /dev/null > "$src"Files.txt
find -s ~/"$src" -type f -exec ls -l {} \; |
awk '{for(i=9;i<=NF;i++) {printf("%s", $i " ")} print ":" $5}' |
grep -v ".DS_Store" | grep -v "Icon\r" |
while read line ; do filespacesize=`basename "$line"`; filesize=`echo "$filespacesize" |
sed -e 's/ :/:/1'`
path=`dirname "$line"`; echo "$filesize:$path" >> "$src"Files.txt ;
done
And it works fine, BUT…
If a filename has > 1 space between parts, I only get 1 space between filename parts, and the colon, followed by the filesize.
How can I get the full filename, :, and then the file size?
It seems you want the following (provided your find handles the printf option with the %f, %s and %h modifiers):
src=Desktop
echo "Constructing $src files list. $(date)"
find ~/"$src" -type f -printf '%f:%s:%h\n' > "$src"Files.txt
Much shorter and much more efficient than your method!
This will not discard the .DS_STORE and Icon\r things… but I'm not really sure what you really want to discard. If you want to discard the .DS_STORE directory altogether:
find ~/"$src" -name '.DS_STORE' -type d -prune -o -type f -printf '%f:%s:%h\n' > "$src"Files.txt
#guido seems to have guessed what you mean by grep -v "Icon\r": ignore files ending with Icon; if this his guess is right, then this will do:
find ~/"$src" -name '.DS_STORE' -type d -prune -o ! -name '*Icon' -type f -printf '%f:%s:%h\n' > "$src"Files.txt

HandBrakeCLI command break while loop?

In a bash script, result of find is
/path/to/file1.nrg
/path/to/file2.nrg
/path/to/file3.nrg
i have this while loop:
process preset
processpreset ()
{
x=$1
# Replace , by -o -iname for file types.
iname=" -o -iname \*."
# Find specified files. Eval allow var prst1_in with find.
eval "find "$fpath" -type f \( -iname \*."${prst_in[x]//,/$iname}" \) -size ${prst_lim_size[x]}" | sort | while read -r i
do
titles=$(HandBrakeCLI --input "$i" --scan |& grep -Po '(?<=DVD has )([0-9]+)')
if (( $titles > 1 )); then
echo "DVD has $titles title(s)"
fi
done
}
the script only echo 1 time File has 8 title(s) after it stop, when using titles="8" the loop echo for all files in folder. Can anyone point me my error please?
EDIT: what work for me, many thanks Anubhava
processpreset ()
{
x=$1
# Replace , by -o -iname for file types.
iname=" -o -iname \*."
# Find specified files. Eval allow var prst1_in with find.
eval "find "$fpath" -type f \( -iname \*."${prst_in[x]//,/$iname}" \) -size ${prst_lim_size[x]}" | sort | while read -r i
do
titles="$(echo ""|HandBrakeCLI --input "$i" --scan |& grep -Po '(?<=DVD has )([0-9]+)')"
if (( $titles > 1 )); then
echo "DVD has $titles title(s)"
fi
done
}
the echo ""| fix the problem.
ok try this script:
while read -r i
do
echo "i is: $i"
titles="$(echo ""|HandBrakeCLI --input "$i" --scan | grep -Po '(?<=DVD has )([0-9]+)')"
if (( titles > 1 )); then
echo "DVD has $titles title(s)"
fi
done < <(find "$imgpath" -type f \( -iname \*.iso -o -iname \*.nrg -o -iname \*.img \) | sort)

Resources