Is there a way to have the find command return a value when it does not find a match? Basically, I have an old backup, and I want to search for each of the files in it on my current computer. Here is a cheesy way I was going to do it:
first run the following from the home directory:
$ ls -lR * > AllFiles.txt;
This will build my listing of all of my files on the current computer.
Next run the following script for each file in the back up:
#! /bin/bash
if ! grep $1 ~/AllFiles.txt; then
echo file $1 not found;
exit 1;
fi
Of course this is clunky, and it does not account for filename changes, but it's close enough. Alternatively, I'd like to do a find command for each of the back up files.
You can use standard return value test if using a standard gnu find, such as:
find . ! -readable -prune -o -name 'filenameToSearch.ext'
then check for return value using:
echo $?
if any value other than 0 means it did not find a match.
If I understood you correctly;
grep -r valueORpatternToSearchinTEXT $(find . -type f) |wc -l
This will find for every file in the working/existing directory you are and its subdirs, search for what you need, then count for lines, if it is not found, you will get 0 at the end. Remove pipe and afterwards if you want to see what is found and where.
Related
I have bash script that intents to find all files older then "X" minutes and to redirect the output into a file. The logic is a have a for loop and i want to do a find through all files, but for some reason it prints and redirect in the output file just the file from the last directory(TESTS[3]="/tmp/test/"). So i want all the files from the directories to be redirected there. Thank you for the help :D
Here is the sh:
#!/bin/bash
set -x
if [ ! -d $TEST ]
then
echo "The directory does not exist (${TEST})!"
echo "Aborted."
exit 1
fi
TESTS[0]="/tmp/t1/"
TESTS[1]="/tmp/t2/"
TESTS[2]="/tmp/t3/"
TESTS[3]="/tmp/test/"
for TEST in "${TESTS[#]}"
do
find $TEST -type f -mmin +1 -exec ls -ltrah {} \; > /root/alex/out
done
You are using > inside the loop to redirect the output of the latest command to the file each time, overwriting the previous contents of the file. If you used >> it would open the file in "append" mode each time instead, but...
A better way to fix your issue would be by moving the redirection to outside the loop:
done > /root/alex/out
And an even better way than that would be to avoid a loop entirely and just use:
find "${TESTS[#]}" -type f -mmin +1 -exec ls -ltrah {} \; > /root/alex/out
Since find accepts multiple paths.
I think you can use {} + instead of {} \; to call the minimum number of ls required to process all arguments, and you might want to check -printf in man find because you can probably get a similar output using built-in format specifiers without calling ls at all.
I am flattening a directory of nested folders/picture files down to a single folder. I want to move all of the nested files up to the root level.
There are 3,381 files (no directories included in the count). I calculate this number using these two commands and subtracting the directory count (the second command):
find ./ | wc -l
find ./ -type d | wc -l
To flatten, I use this command:
find ./ -mindepth 2 -exec mv -i -v '{}' . \;
Problem is that when I get a count after running the flatten command, my count is off by 46. After going through the list of files before and after (I have a backup), I found that the mv command is overwriting files sometimes even though I'm using -i.
Here's details from the log for one of these files being overwritten...
.//Vacation/CIMG1075.JPG -> ./CIMG1075.JPG
..more log
..more log
..more log
.//dog pics/CIMG1075.JPG -> ./CIMG1075.JPG
So I can see that it is overwriting. I thought -i was supposed to stop this. I also tried a -n and got the same number. Note, I do have about 150 duplicate filenames. Was going to manually rename after I flattened everything I could.
Is it a timing issue?
Is there a way to resolve?
NOTE: it is prompting me that some of the files are overwrites. On those prompts I just press Enter so as not to overwrite. In the case above, there is no prompt. It just overwrites.
Apparently the manual entry clearly states:
The -n and -v options are non-standard and their use in scripts is not recommended.
In other words, you should mimic the -n option yourself. To do that, just check if the file exists and act accordingly. In a shell script where the file is supplied as the first argument, this could be done as follows:
[ -f "${1##*/}" ]
The file, as first argument, contains directories which can be stripped using ##*/. Now simply execute the mv using ||, since we want to execute when the file doesn't exist.
[ -f "${1##*/}" ] || mv "$1" .
Using this, you can edit your find command as follows:
find ./ -mindepth 2 -exec bash -c '[ -f "${0##*/}" ] || mv "$0" .' '{}' \;
Note that we now use $0 because of the bash -c usage. It's first argument, $0, can't be the script name because we have no script. This means the argument order is shifted with respect to a usual shell script.
Why not check if file exists, prior move? Then you can leave the file where it is or you can rename it or do something else...
Test -f or, [] should do the trick?
I am on tablet and can not easyly include the source.
I need to get the first file in a folder which has the .tar.gz extension. I came up with:
FILE=/path/to/folder/$(ls /path/to/folder | grep ".tar.gz$" | head -1)
but I feel it can be done simpler and more elegant. Is there a better solution?
You could get all the files in an array, and then get the desired one:
files=( /path/to/folder/*.tar.gz )
Getting the first file:
echo "${files[0]}"
Getting the last file:
echo "${files[${#files[#]}-1]}"
You might want to set the shell option nullglob to handle cases when there are no matching files:
shopt -s nullglob
here is the shorter version from your own idea.
FILE=$(ls /path/to/folder/*.tar.gz| head -1)
You can use set as shown below. The shell will expand the wildcard and set will assign the files as positional parameters which can be accessed using $1, $2 etc.
# set nullglob so that if no matching files are found, the wildcard expands to a null string
shopt -s nullglob
set -- /path/to/folder/*.tar.gz
# print the name of the first file
echo "$1"
It is not good practice to parse ls as you are doing, because it will not handle filenames containing newline characters. Also, the grep is unnecessary because you could simply do ls /path/to/folder/*.tar.gz | head -1.
Here's a way to accomplish it:
for FILE in *.tar.gz; do break; done
You tell bash to break the loop in the first iteration, just when the first filename is assigned to FILE.
Another way to do the same:
first() { FILE=$1; } && first *.tar.gz
Here you are using the positional parameters of the function first which is better than set the positional parameters of your entire bash process (as with set --).
Here's a find based solution:
$ find . -maxdepth 1 -type f -iname "*.tar.gz" | head -1
where:
. is the current directory
-maxdepth 1 means only check the current directory
-type f means only look at files
-iname "*.tar.gz" means do a case-insensitive search for any file with the .tar.gz extension
| head -1 takes the results of find and only returns the first line
You could get rid of the | head -1 by doing something like:
$ find . -maxdepth 1 -type f -iname "*.tar.gz" -maxdepth 1 -print -quit
But I'm actually not sure how portable -print -quit is across environments (it works on MacOS and Ubuntu though).
I'm working on a unix script that has 2 input parameters - path and size.
The script will check all the files in the given path with the given size and deletes them. If the delete operation fails, the respective file-name is recorded into a file. For any other case, the file is rendered without any action.
I have written a short code (don't know whether it works).
find $path -type f -size +${byte_size}c -print | xargs -I {}
if $?=1;
then
rm -rf {};
else
echo {} >> Error_log_list.txt'
where
$path is the path where we search for the files.
size is the input size.
Error_log_list.txt is the file where we send the non-deletable filenames.
Can anyone please help me verify whether it is correct?
GNU find has a -delete option for this exact use case. More information (and a number of different approaches) in the find documentation.
find $path -type f -size +${byte_size}c -delete
Executing your script results in the following syntax error:
./test.sh: line 9: unexpected EOF while looking for matching `''
./test.sh: line 11: syntax error: unexpected end of file
Moreover the condition of the if statement seems not correct.
If I am not wrong it tests the return code of the "rm" command before to
execute the command.
I am not familiar with xargs and I tried to rewrite your script
using a while loop construct. Here my script
#!/bin/bash
path=$1
byte_size=$2
find $path -type f -size +${byte_size}c -print | while read file_name
do
rm -f $file_name
if [ ! $? -eq 0 ]; then
echo $file_name >> Error_log_list.txt
fi
done
I tested it trying to delete files without the right permission and it works.
I wrote a script, please check this functionality
a=`find . -type f -size +{$size}c -print`
#check if $a is empty
if [ -z "$a" ]
then
echo $a > error_log.txt
#if a is not empty then remove them
else
rm $a
fi
Let me explain what we are doing here.
First assigning the file_names in current directory (which satisfy
size requirement) to a variable 'a'
Checking if that variable is
empty (empty means there is no file with your size requirement) if a
has some values then delete them
/usr/local/bin/growlnotify -m 'Looking for subtitles...'
found='find /Users -type d -mmin -1'
found1='find $found/*.txt'
if [ -d "$found1" ];
then
/usr/local/bin/growlnotify -m "Subtitles downloaded!"
else
/usr/local/bin/growlnotify -m "Could not download subtitles"
fi
I am trying to write a bash script that would locate the folder in which an app downloaded subtitles and inform user using growl if they are present or not.
$found gives me a list of directories, but I do not know how to get to the one I want..
Please help =)
Sorry for my english
thanks for the answers! This is what I used, and what seems to be working just fine:
for FILE in "$#"
do
if [ -e "${FILE%.*}.txt" ];
then
/usr/local/bin/growlnotify -a iNapi -m "Napisy zostały pobrane!"
else
/usr/local/bin/growlnotify -a iNapi -m "Nie udało się pobrać napisów."
fi
done
Basically you have some errors in the script, besides them, I dont think it's the correct way to do it.
Anyway, first of, you should do:
found=`find /Users -type d`
(note the use of ` and not ')
That will store in $found a list of directories under /Users, the -mmin 1 param just list those dirs that were created in the last minute, if that's correct, just add it again.
Later that you need to loop the results to look for txt files:
for d in $found; do
#here you do ll or find for .txt files, using $d as dir
done
That way isn't the best for me, I think that you can just do:
find /Users -name *.txt
and then see what you got, the find output will print the directory where each txt file resides and that's the same that you are trying to do, but only one step.