dont list files in the mentioned directories - bash

I have three directories with sub directories the parent directory are a, b, & c. a1&a2 are subdirectories of a, b1&b2 are subdirectories of b, c1&c2 are subdirectories of c. The files are arranged like this format a.txt(a), a1.txt(a1), a2.txt(a2), b.txt(b), b1.txt(b1), b2.txt(b2), c.txt(c), c1.txt(c1), c2.txt(c2). Now I am trying to list the files like i want to ignore the files in the entire directory a with its sub directories a1&a2 and want to igonre the files only in the directory b1 not directory b or b2 and want to ignore the files only in the directory c2 not the directories c or c1. Script which I have tried is below
#!/bin/sh
find * -type d | while IFS= read d
do
dirname=`basename $d`
if [ ${dirname} != "a" ] || [ ${dirname} != "b1" ] || [ ${dirname} != "c2" ]
then
cd $dirname
find * ! -name . -prune -type f | while read fname
do
fname=`basename $fname`
echo $fname
done
fi
done
The results are of the above script is
a.txt
a2.txt
b.sh: a1: does not exist
May i know what is the mistake i am doing in it.

To reproduce test
mkdir -p {a/a,b/b,c/c}{1,2}
touch {a/a,b/b,c/c}.txt
for d in {a/a,b/b,c/c}{1,2}; do touch $d/${d#*/}.txt; done
find . -type f
./a/a.txt
./a/a1/a1.txt
./a/a2/a2.txt
./b/b.txt
./b/b1/b1.txt
./b/b2/b2.txt
./c/c.txt
./c/c1/c1.txt
./c/c2/c2.txt
The find command
find . \( -path ./a -o -path ./b/b1 -o -path ./c/c2 \) -prune -o -print
Explanation
-prune over a directory will prevent find to descend into it, is applied over condition between escaped parenthesis -o equivalent to -or, however it returns true on matching files, after the next -o conditions can be added, (by default junction is -a, -and which can be ommited) and action (-printf -ls -exec etc.).
Other example :
find . \( -path ./a -o -path ./b/b1 -o -path ./c/c2 \) -prune -type f -o -type f
./b/b.txt
./b/b2/b2.txt
./c/c.txt
./c/c1/c1.txt
EDIT after comment:
find . -type d \( -name a -o -name b1 -o -name c2 \) -prune -type f -o -type f -printf '%f\n'
b.txt
b2.txt
c.txt
c1.txt
EDIT after last comment (SunOS find doesn't have -printf) :
find . -type d \( -name a -o -name b1 -o -name c2 \) -prune -o -type f -exec sh -c 'for f; do d=${f%/*}; echo "${d##*/}" "${f##*/}"; done' sh-echo {} +
b b.txt
b2 b2.txt
c c.txt
c1 c1.txt

Related

find printf what is correct format in crontab?

i want to save a list of files and i want to do in cron but i dont know how to convert this command
echo $(find /tmp -type f -printf "%p||%s||||||") > /share/Public/serwer/test33.data
in terminal this command works ok but in cron is smothing wrong, file is empty, why?
i tried add slashes before variables %s %p
i tried many many other combinations:
echo $(find /tmp -type f -printf "%p||%s||||||") > /share/Public/serwer/test33.data
echo $(find /tmp -type f -printf "%p||%s||||||") > /share/Public/serwer/test32.data
echo $(find /tmp -type f) > $TMP_DIR/test31.data
echo $(find $BACKUP_DIR -type f -printf \%p) > $TMP_DIR/test30.data
echo $(find $BACKUP_DIR -type f -printf %p) > $TMP_DIR/test28.data
echo $(find $BACKUP_DIR -type f) > $TMP_DIR/test27.data
echo $(find $BACKUP_DIR -type f -printf "ab") > $TMP_DIR/test26.data
echo $(find $BACKUP_DIR -type f -printf "||") > $TMP_DIR/test25.data
echo $(find $BACKUP_DIR -type f -printf "%p||%s\r\n") > $TMP_DIR/test01.data
echo $(find $BACKUP_DIR -type f -printf "\%p||\%s\r\n") > $TMP_DIR/test02.data
echo $(find $BACKUP_DIR -type f -printf "\%p") > $TMP_DIR/test03.data
echo $(find $BACKUP_DIR -type f -printf "\%s") > $TMP_DIR/test04.data
echo $(find $BACKUP_DIR -type f -printf "\\%s") > $TMP_DIR/test05.data
echo $(find $BACKUP_DIR -type f -printf "\\%p") > $TMP_DIR/test06.data
echo $(find $BACKUP_DIR -type f -printf "\r\n") > $TMP_DIR/test07.data
echo $(find $BACKUP_DIR -type f -printf) > $TMP_DIR/test10.data
echo $(find $BACKUP_DIR -type f -printf) > $TMP_DIR/test11.data
echo $(find $BACKUP_DIR -type f -printf "%p") > $TMP_DIR/test12.data
echo $(find $BACKUP_DIR -type f -printf "\%p") > $TMP_DIR/test13.data
echo `find $BACKUP_DIR -type f -printf "%p"` > $TMP_DIR/test14.data
echo `find $BACKUP_DIR -type f -printf "\\\\%p"` > $TMP_DIR/test15.data
echo `find $BACKUP_DIR -type f -printf \%p` > $TMP_DIR/test16.data
echo `find $BACKUP_DIR -type f -printf '\%p'` > $TMP_DIR/test17.data
echo `find $BACKUP_DIR -type f -printf '\\%p'` > $TMP_DIR/test18.data
echo `find $BACKUP_DIR -type f -printf '\\\%p'` > $TMP_DIR/test19.data
echo `find $BACKUP_DIR -type f -printf '\\\\%p'` > $TMP_DIR/test20.data
echo `find $BACKUP_DIR -type f -printf \%'s` > $TMP_DIR/test21.data
echo `find $BACKUP_DIR -type f -printf \%'p` > $TMP_DIR/test22.data
echo `find $BACKUP_DIR -type f -printf \%'p'` > $TMP_DIR/test23.data
echo `find $BACKUP_DIR -type f -printf \%'s'` > $TMP_DIR/test24.data
nothing work
This crontab entry should work :
* * * * * find /tmp -type f -printf "\%p||\%s||||||\n" > /share/Public/serwer/test33.data 2>/tmp/crontab.err
i added full path to command find, now it works correctly
/share/CACHEDEV1_DATA/.qpkg/Qapache/bin/find $BACKUP_DIR -type f -printf "%p||%s\r"
thanks for help
Thanks Philippe i do that and i have smothing like this:
find: unrecognized: -printf
BusyBox v1.24.1 (2021-09-23 02:31:15 CST) multi-call binary.
Usage: find [-HL] [PATH]... [OPTIONS] [ACTIONS]
Search for files and perform actions on them.
First failed action stops processing of current file.
Defaults: PATH is current directory, action is '-print'
-L,-follow Follow symlinks
-H ...on command line only
-xdev Don't descend directories on other filesystems
-maxdepth N Descend at most N levels. -maxdepth 0 applies
actions to command line arguments only
-mindepth N Don't act on first N levels
-depth Act on directory *after* traversing it
Actions:
( ACTIONS ) Group actions for -o / -a
! ACT Invert ACT's success/failure
ACT1 [-a] ACT2 If ACT1 fails, stop, else do ACT2
ACT1 -o ACT2 If ACT1 succeeds, stop, else do ACT2
Note: -a has higher priority than -o
-name PATTERN Match file name (w/o directory name) to PATTERN
-iname PATTERN Case insensitive -name
-path PATTERN Match path to PATTERN
-ipath PATTERN Case insensitive -path
-regex PATTERN Match path to regex PATTERN
-type X File type is X (one of: f,d,l,b,c,...)
-perm MASK At least one mask bit (+MASK), all bits (-MASK),
or exactly MASK bits are set in file's mode
-mtime DAYS mtime is greater than (+N), less than (-N),
or exactly N days in the past
-mmin MINS mtime is greater than (+N), less than (-N),
or exactly N minutes in the past
-newer FILE mtime is more recent than FILE's
-inum N File has inode number N
-user NAME/ID File is owned by given user
-group NAME/ID File is owned by given group
-size N[bck] File size is N (c:bytes,k:kbytes,b:512 bytes(def.))
+/-N: file size is bigger/smaller than N
-links N Number of links is greater than (+N), less than (-N),
or exactly N
-prune If current file is directory, don't descend into it
If none of the following actions is specified, -print is assumed
-print Print file name
-print0 Print file name, NUL terminated
-exec CMD ARG ; Run CMD with all instances of {} replaced by
file name. Fails if CMD exits with nonzero
-exec CMD ARG + Run CMD with {} replaced by list of file names
-delete Delete current file/directory. Turns on -depth option

Rename file if it is the only one with the extension in directory

This works however I would like to do it only if it is the only .jpg for the given directory, the one below will just rename them all to folder.jpg, overwriting the other files:
find . -type f -name '*.jpg' -execdir mv {} 'folder.jpg' \;
I guess find cannot filter by the number of matches, but you can always exec a shell which does more elaborate checks for you:
find . -type f -name '*.jpg' -execdir sh -c '[ $# = 1 ] && mv "$1" folder.jpg' sh {} +

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.

How should I search a group of files using linux `find` command?

I have a group of files in a certain directory. Now I want to search them in two different directories. I used below code:
(jumped to the directory that contains that group of files)
ls | while read name; do find ~/dir1 ~/dir2 -name {$name};done
But I guess it is too slow since for each file, dir1 and dir2 should be searched once. So the search will be do too many times.
Is my guess right? and if so, what should I write?
find supports -o for OR operation.
You can use this:
files=(); # Initialize an empty bash array
for i in *; do files+=(-o -name "$i"); done # Add names of all the files to the array
find dir1/ dir2/ -type f '(' "${files[#]:1}" ')' # Search for those files
e.g., consider this case:
$ touch a b c
$ ls
a b c
$ files=()
$ for i in *; do files+=(-o -name "$i"); done
$ printf '%s ' "${files[#]}"; echo
-o -name a -o -name b -o -name c
$ printf '%s ' "${files[#]:1}"; echo
-name a -o -name b -o -name c
$ printf '%s ' find dir1/ dir2/ -type f '(' "${files[#]:1}" ')'; echo
find dir1/ dir2/ -type f ( -name a -o -name b -o -name c ) # This is the command that actually runs.

ls command and size of files in shell script

count=0; #count for counting
IFS='
'
for x in `ls -l $input`; #for loop using ls command
do
a=$(ls -ls | awk '{print $6}') #print[6] is sizes of file
echo $a
b=`echo $a | awk '{split($0,numbers," "); print numbers[1]}'`
echo $b
if [ $b -eq 0 ] # b is only size of a file
then
count=`expr $count + 1` #if b is zero , the count will increase one by one
fi
echo $count
done
I want to find 0 size files . I do that using find command. The second thing is I want to count number of has 0 size of files using ls command and awk. But It doesn't true code . What is my mistake ?
The -s test is true if a file has non-zero size. If that test fails for file, increment your empty-file count.
empty_files=0
for f in "$input"/*; do
[ -s "$f" ] || : $(( empty_files++ ))
done
Your main mistake is that you're parsing ls!
If you want to find (regular) files that are empty, and if you have a version of find that supports the -empty predicate, use it:
find . -type f -empty
Note that this will recurse in subfolders too; if you don't want that, use:
find . -maxdepth 1 -type f -empty
(assuming that your find also supports -maxdepth).
If you only want to count how many empty (regular) files you have:
find . -maxdepth 1 -type f -empty -printf x | wc -m
and if you want to perform both operations at the same time, i.e., print out the name or save them in an array for future use, and count them:
empty_files=()
while IFS= read -r -d '' f; do
empty_files+=( "$f" )
done < <(find . -maxdepth 1 -type f -empty -print0)
printf 'There are %d empty files:\n' "${#empty_files[#]}"
printf ' %s\n' "${empty_files[#]}"
With Bash≥4.4, you could use mapfile instead of the while-read loop:
mapfile -t -d '' empty_files < <(find . -maxdepth 1 -type f -empty -print0)
printf 'There are %d empty files:\n' "${#empty_files[#]}"
printf ' %s\n' "${empty_files[#]}"
For a POSIX-compliant way, use test with the -s option:
find . -type f \! -exec test -s {} \; -print
and if you don't want to recurse into subdirectories, you'll have to -prune them:
find . \! -name . -prune -type f \! -exec test -s {} \; -print
and if you want to count them:
find . \! -name . -prune -type f \! -exec test -s {} \; -exec printf x | wc -m
and here, if you want to perform both operations (count them and save them in an array for later use), use the previous while-read loop (or mapfile if you live in the future) with this find:
find . \! -name . -prune -type f \! -exec test -s {} \; -exec printf '%s\0' {} \;
Also see chepner's answer for a pure shell solution (needs minor tweaking to be POSIX compliant).
Regarding your comment
I want to count and delete [empty files]. How can I do that at the same time?
If you have GNU find (or a find that supports all the goodies):
find . -maxdepth 1 -type f -empty -printf x -delete | wc -m
if not,
find . \! -name . -prune -type f \! -exec test -s {} \; -printf x -exec rm {} \; | wc -m
Make sure that the -delete (or -exec rm {} \;) predicate is at the end! do not exchange the order of the predicates!

Resources