gzip already has gz suffix unchanged in the script - linux

I have created a script to zip and move log files from one directory to another directory to free space. This is the script:
#!/bin/bash
logsDirectory="/test//logs/"
email=""
backupDirectory="/test/backup"
pid="/data/test/scripts/backup.pid"
usage=$(df | grep /data/logs | awk '{ print $2 }')
space=450000000
getBackup ()
{
if [[ ! -e $pid ]] then
if [[ $usage -le $space ]]
then
touch $pid
find $backupDirectory -mtime +15 -type f -delete;
for i in $(find $logsDirectory -type f -not -path "*/irws/*")
do
/sbin/fuser $i > /dev/null 2>&1
if [ $? -ne 0 ]
then
gzip $i
mv -v $i.gz $backupDirectory
else
continue
fi
done
[[ ! -z $email ]] && echo "Backup is ready" | mas"Backup" $email
rm -f $pid
fi
fi
}
getBackup
I am getting this error:
gzip: /data/logs/log01.log.gz already has .gz suffix -- unchanged
mv: cannot stat `/data/logs/log01.log.gz': No such file or directory
I got the error every time I ran the script in my DEV and PROD (CentOS servers) environments. To analyse it, I ran the same script in a VM (Ubuntu) in my laptop, and I don't get the error there.
My questions:
How can I prevent this error?
What I have done wrong in the script?

Your script contains a number of common clumsy or inefficient antipatterns. Here is a refactoring. The only real change is skipping any *.gz files.
#!/bin/bash
logsDirectory="/test//logs/"
email=""
backupDirectory="/test/backup"
pid="/data/test/scripts/backup.pid"
# Avoid useless use of grep -- awk knows how to match a regex
# Better still run df /data/logs
usage=$(df /data/logs/ | awk '{ print $2 }')
space=450000000
getBackup ()
{
# Quote variables
if [[ ! -e "$pid" ]]; then
if [[ "$usage" -le "$space" ]]; then
touch "$pid"
find "$backupDirectory" -mtime +15 -type f -delete;
# Exclude *.gz files
# This is still not robust against file names with spaces or wildcards in their names
for i in $(find "$logsDirectory" -type f -not -path "*/irws/*" -not -name '*.gz')
do
# Avoid useless use of $?
if /sbin/fuser "$i" > /dev/null 2>&1
then
gzip "$i"
mv -v "$i.gz" "$backupDirectory"
# no need for do-nothing else
fi
done
[[ ! -z "$email" ]] &&
echo "Backup is ready" | mas"Backup" "$email"
rm -f "$pid"
fi
fi
}
getBackup
With a slightly more intrusive refactoring, the proper fix to the find loop would perhaps look something like
find "$logsDirectory" -type f \
-not -path "*/irws/*" -not -name '*.gz' \
-exec sh -c '
for i; do
if /sbin/fuser "$i" > /dev/null 2>&1
then
gzip "$i"
mv -v "$i.gz" "$backupDirectory"
fi
done' _ {} +
where the secret sauce is to have find ... -exec + pass in the arguments to the sh -c script in a way which does not involve exposing the arguments to the current shell at all.

What I have done wrong in the script?
Your script tries to zip every file but the gzip command is rejecting files already zipped
How can I prevent this error?
Have the script check whether the file is zipped or not and only gzip if it corresponds (1). Alternatively, you could force re-compression even if it is already compressed (2).
Going with option number 1):
getBackup ()
{
if [[ ! -e $pid ]] then
if [[ $usage -le $space ]]
then
touch $pid
find $backupDirectory -mtime +15 -type f -delete;
for i in $(find $logsDirectory -type f -not -path "*/irws/*")
do
/sbin/fuser $i > /dev/null 2>&1
if [ $? -ne 0 ]
then
if [[ $i =~ \.gz$ ]]
# File is already zipped
mv -v $i $backupDirectory
else
gzip $i
mv -v $i.gz $backupDirectory
fi
else
continue
fi
done
[[ ! -z $email ]] && echo "Backup is ready" | mas"Backup" $email
rm -f $pid
fi
fi
}

Related

Bypass "-" processing as arithmetic operator in bash

#!/bin/bash
dir=$1
cd $dir
list=$(find . -maxdepth 1 -mindepth 1 -type d -printf '%f,')
IFS=',' read -a array <<< "${list}"
for i in "${array[#]}"
do
longPermission=$(getfacl $i |stat -c %A $i)
longOwner=$(getfacl $i |grep owner)
ownerPermission=${longPermission:1:3}
owner=${longOwner:9}
if (($ownerPermission=="rwx"))
then
permission='FULL_PERMISSION'
fi
if (($ownerPermission=="rw-"))
then
permission='READ/WRITE_PERMISSION'
fi
#if (($ownerPermission=="r--"))
#then
# permission='READ_ONLY_PERMISSION'
#fi
echo ’BUERO\ $owner -user -group -type windows permission $permission’
done
Bash treats the "-" in rw- and rw-- as arithmetic operators but what I wanto to achieve is processing it as string because I want to work with output I get from getfacl $i |grep owner which gives me back a string I wanna echo in
echo ’BUERO\ $owner -user -group -type windows permission $permission’
thanks in advance!
Use if [[ $ownerPermission == "rwx" ]] and if [[ $ownerPermission == "rw-" ]]
The (( expr )) is used for arithemtic expressions, which you don't need here.

Find using file for folder locations Linux Bash

I am trying to use a txt file to store folder locations to use in find command. But keep getting folder not found works with only one folder location in file
with "$addfolder"
found=$(find "$addfolder" ! -path "*/.bak/*" -type f -iname "*$ffind*" | sort)
and replacing \"
addfolder="$addfolder $Folder"
folder.txt :-
Main/Public
Main/General
Not Used
Old Backup Files
#!/bin/bash
addfolder=""
filename="Settings/folders.txt"
#Read Folder.txt for locations
while read -r Folder; do
if [ ! "$Folder" == "" ];then
if [ -d "$Folder" ]; then
addfolder="$addfolder \"$Folder\""
echo "$addfolder"
fi
fi
done < "$filename"
if [ "$addfolder" == "" ]; then
exit
fi
echo -e "\e[36mEnter Filename To Find :-\e[0m"
read -p "" ffind
echo -e "\e[92mSearching:\e[0m"
found=$(find $addfolder ! -path "*/.bak/*" -type f -iname "*$ffind*" | sort)
echo -e "\e[33m$found\e[0m"
echo "Press Enter To Exit"
read -s -n 1 -p ""
Regular variables should only hold single strings.
To hold lists of strings, use an array:
#!/bin/bash
addfolder=()
filename="Settings/folders.txt"
#Read Folder.txt for locations
while IFS= read -r Folder; do
if [ ! "$Folder" == "" ];then
if [ -d "$Folder" ]; then
addfolder+=( "$Folder" )
echo "${addfolder[#]}"
fi
fi
done < "$filename"
if [ "${#addfolder[#]}" == 0 ]; then
exit
fi
echo -e "\e[36mEnter Filename To Find :-\e[0m"
read -p "" ffind
echo -e "\e[92mSearching:\e[0m"
found=$(find "${addfolder[#]}" ! -path "*/.bak/*" -type f -iname "*$ffind*" | sort)
echo -e "\e[33m$found\e[0m"
echo "Press Enter To Exit"
read -s -n 1 -p ""

remove files and prompt directories only

As I was deleting many obsolete file trees on a Linux machine I was wondering if there is an easy way to remove files recursively while prompting only on directories.
I could use rm -ri but there some much files that it would be really annoying to answer for every one of them. What really matter to me is being prompted on folders to have more control on what happens.
I am not a bash expert so I am asking if there is a simple way to do this.
Here is my attempt with a long bash script:
#!/bin/bash
promptRemoveDir()
{
fileCount=$(ls -1 $1 | wc -l)
prompt=1
while [ $prompt == 1 ]
do
read -p "remove directory: $1($fileCount files) ? [yl]: " answer
case $answer in
[yY])
rm -r $1
prompt=0
;;
l)
echo $(ls -A $1)
;;
*)
prompt=0
;;
esac
done
}
removeDir()
{
if [ "$(ls -A $1)" ]
then dirs=$(find $1/* -maxdepth 0 -type d)
fi
if [[ -z $dirs ]]
then
promptRemoveDir $1
else
for dir in $dirs
do
removeDir $dir
done
promptRemoveDir $1
fi
}
for i in $*
do
if [ -d $i ]
then
removeDir $i
else
rm $i
fi
done
If i understand your question properly this should work
Dirs=$(find . -type d)
Removes just the files in the directories specified
for i in "$Dirs"; do read -p "Delete files in "$i": ";if [[ $REPLY == [yY] ]]; then find $i -maxdepth 1 -type f | xargs -0 rm ; fi ;done
If you want to delete the folders as well, this will read from lowest directory(none below it) upwards.
for i in $(echo "$Dirs" | sed '1!G;h;$!d' ); do read -p "Delete files in $i: ";if [[ $REPLY == [yY] ]]; then rm -r "$i"; fi ;done
Here's a simplified version from me. There's no need to use ls and find.
#!/bin/bash
shopt -s nullglob
shopt -s dotglob
function remove_dir_i {
local DIR=$1 ## Optional. We can just use $1.
local SUBFILES=("$DIR"/*) FILE
for (( ;; )); do
read -p "Remove directory: $DIR (${#SUBFILES[#]} files)? [YNLQ]: "
case "$REPLY" in
[yY])
echo rm -fr "$DIR"
return 0
;;
[nN])
for FILE in "${SUBFILES[#]}"; do
if [[ -d $FILE ]]; then
remove_dir_i "$FILE" || return 1
# else
# ## Apparently we skip deleting a file. If we do this
# ## we could actually simplify the function further
# ## since we also delete the file at first loop.
# # echo "Removing file \"$FILE.\""
# # rm -f "$FILE"
fi
done
return 0
;;
[lL])
printf '%s\n' "${SUBFILES[#]}"
;;
[qQ])
return 1
;;
# *)
# echo "Please answer Y(es), N(o), L(ist) or Q(uit)."
# ;;
esac
done
}
for FILE; do
if [[ -d $FILE ]]; then
remove_dir_i "$FILE"
else
# echo "Removing file \"$FILE.\""
echo rm -f "$FILE"
fi
done
Remove echo from rm commands when you're sure it's working already. Test:
rm -f /tmp/tar-1.27.1/ABOUT-NLS
rm -f /tmp/tar-1.27.1/acinclude.m4
rm -f /tmp/tar-1.27.1/aclocal.m4
rm -f /tmp/tar-1.27.1/AUTHORS
Remove directory: /tmp/tar-1.27.1/build-aux (12 files)? [YNLQ]: n
Remove directory: /tmp/tar-1.27.1/build-aux/snippet (5 files)? [YNLQ]: n
rm -f /tmp/tar-1.27.1/ChangeLog
rm -f /tmp/tar-1.27.1/ChangeLog.1
rm -f /tmp/tar-1.27.1/config.h.in
rm -f /tmp/tar-1.27.1/configure
rm -f /tmp/tar-1.27.1/configure.ac
rm -f /tmp/tar-1.27.1/COPYING
Remove directory: /tmp/tar-1.27.1/doc (25 files)? [YNLQ]: n
Remove directory: /tmp/tar-1.27.1/gnu (358 files)? [YNLQ]: n
Remove directory: /tmp/tar-1.27.1/gnu/uniwidth (2 files)? [YNLQ]: n
rm -f /tmp/tar-1.27.1/INSTALL
Remove directory: /tmp/tar-1.27.1/lib (19 files)? [YNLQ]:
...
Actually I just came upon the -depth option of the find command that is exactly what I was looking for. I can't believe I just missed that:
-depth Process each directory's contents before the directory itself. The -delete action also implies -depth.
So similar to #Jidder's code, I can write this:
dirs=$(find ./test_script -depth -type d); for i in $dirs; do read -p "Delete files in $i? " REPLY; if [[ $REPLY == [yY] ]]; then rm -r $i; fi; done;
And for more readability:
dirs=$(find ./test_script -depth -type d)
for i in $dirs
do
read -p "Delete files in $i? " REPLY
if [[ $REPLY == [yY] ]]
then rm -r $i
fi
done;

Archive old files only AND re-construct folder tree in archive

I want to move all my files older than 1000 days, which are distributed over various subfolders, from /home/user/documents into /home/user/archive. The command I tried was
find /home/user/documents -type f -mtime +1000 -exec rsync -a --progress --remove-source-files {} /home/user/archive \;
The problem is, that (understandably) all files end up being moved into the single folder /home/user/archive. However, what I want is to re-construct the file tree below /home/user/documents inside /home/user/archive. I figure this should be possible by simply replacing a string with another somehow, but how? What is the command that serves this purpose?
Thank you!
I would take this route instead of rsync:
Change directories so we can deal with relative path names instead of absolute ones:
cd /home/user/documents
Run your find command and feed the output to cpio, requesting it to make hard-links (-l) to the files, creating the leading directories (-d) and preserve attributes (-m). The -print0 and -0 options use nulls as record terminators to correctly handle file names with whitespace in them. The -l option to cpio uses links instead of actually copying the files, so very little additional space is used (just what is needed for the new directories).
find . -type f -mtime +1000 -print0 | cpio -dumpl0 /home/user/archives
Re-run your find command and feed the output to xargs rm to remove the originals:
find . -type f -mtime +1000 -print0 | xargs -0 rm
Here's a script too.
#!/bin/bash
[ -n "$BASH_VERSION" ] && [[ BASH_VERSINFO -ge 4 ]] || {
echo "You need Bash version 4.0 to run this script."
exit 1
}
# SOURCE=/home/user/documents/
# DEST=/home/user/archive/
SOURCE=$1
DEST=$2
declare -i DAYSOLD=10
declare -a DIRS=()
declare -A DIRS_HASH=()
declare -a FILES=()
declare -i E=0
# Check directories.
[[ -n $SOURCE && -d $SOURCE && -n $DEST && -d $DEST ]] || {
echo "Source or destination directory may be invalid."
exit 1
}
# Format source and dest variables properly:
SOURCE=${SOURCE%/}
DEST=${DEST%/}
SOURCE_LENGTH=${#SOURCE}
# Copy directories first.
echo "Creating directories."
while read -r FILE; do
DIR=${FILE%/*}
if [[ -z ${DIRS_HASH[$DIR]} ]]; then
PARTIAL=${DIR:SOURCE_LENGTH}
if [[ -n $PARTIAL ]]; then
TARGET=${DEST}${PARTIAL}
echo "'$TARGET'"
mkdir -p "$TARGET" || (( E += $? ))
chmod --reference="$DIR" "$TARGET" || (( E += $? ))
chown --reference="$DIR" "$TARGET" || (( E += $? ))
touch --reference="$DIR" "$TARGET" || (( E += $? ))
DIRS+=("$DIR")
fi
DIRS_HASH[$DIR]=.
fi
done < <(exec find "$SOURCE" -mindepth 1 -type f -mtime +"$DAYSOLD")
# Copy files.
echo "Copying files."
while read -r FILE; do
PARTIAL=${FILE:SOURCE_LENGTH}
cp -av "$FILE" "${DEST}${PARTIAL}" || (( E += $? ))
FILES+=("$FILE")
done < <(exec find "$SOURCE" -mindepth 1 -type f -mtime +"$DAYSOLD")
# Remove old files.
if [[ E -eq 0 ]]; then
echo "Removing old files."
rm -fr "${DIRS[#]}" "${FILES[#]}"
else
echo "An error occurred during copy. Not removing old files."
exit 1
fi

Bash Script to process data containing input string

I am trying to create a script that will find all the files in a folder that contain, for example, the string 'J34567' and process them. Right now I can process all the files in the folder with my code, however, my script will not just process the contained string it will process all the files in the folder. In other words once I run the script even with the string name ./bashexample 'J37264' it will still process all the files even without that string name. Here is my code below:
#!/bin/bash
directory=$(cd `dirname .` && pwd)
tag=$1
echo find: $tag on $directory
find $directory . -type f -exec grep -sl "$tag" {} \;
for files in $directory/*$tag*
do
for i in *.std
do
/projects/OPSLIB/BCMTOOLS/sumfmt_linux < $i > $i.sum
done
for j in *.txt
do
egrep "device|Device|\(F\)" $i > $i.fail
done
echo $files
done
Kevin, you could try the following:
#!/bin/bash
directory='/home'
tag=$1
for files in $directory/*$tag*
do
if [ -f $files ]
then
#do your stuff
echo $files
fi
done
where directory is your directory name (you could pass it as a command-line argument too) and tag is the search term you are looking for in a filename.
Following script will give you the list of files that contain (inside the file, not in file name) the given pattern.
#!/bin/bash
directory=`pwd`
tag=$1
for file in $(find "$directory" -type f -exec grep -l "$tag" {} \;); do
echo $file
# use $file for further operations
done
What is the relevance of .std, .txt, .sum and .fail files to the files containing given pattern?
Its assumed there are no special characters, spaces, etc. in file names.
If that is the case following should help working around those.
How can I escape white space in a bash loop list?
Capturing output of find . -print0 into a bash array
There are multiple issues in your script.
Following is not required to set the operating directory to current directory.
directory=$(cd `dirname .` && pwd)
find is executed twice for the current directory due to $directory and ..
find $directory . -type f -exec grep -sl "$tag" {} \;
Also, result/output of above find is not used in for loop.
For loop is run for files in the $directory (sub directories not considered) with their file name having the given pattern.
for files in $directory/*$tag*
Following for loop will run for all .txt files in current directory, but will result in only one output file due to use of $i from previous loop.
for j in *.txt
do
egrep "device|Device|\(F\)" $i > $i.fail
done
This is my temporary solution. Please check if it follows your intention.
#!/bin/bash
directory=$(cd `dirname .` && pwd) ## Should this be just directory=$PWD ?
tag=$1
echo "find: $tag on $directory"
find "$directory" . -type f -exec grep -sl "$tag" {} \; ## Shouldn't you add -maxdepth 1 ? Are the files listed here the one that should be processed in the loop below instead?
for file in "$directory"/*"$tag"*; do
if [[ $file == *.std ]]; then
/projects/OPSLIB/BCMTOOLS/sumfmt_linux < "$file" > "${file}.sum"
fi
if [[ $file == *.txt ]]; then
egrep "device|Device|\(F\)" "$file" > "${file}.fail"
fi
echo "$file"
done
Update 1
#!/bin/bash
directory=$PWD ## Change this to another directory if needed.
tag=$1
echo "find: $tag on $directory"
while IFS= read -rd $'\0' file; do
echo "$file"
case "$file" in
*.std)
/projects/OPSLIB/BCMTOOLS/sumfmt_linux < "$file" > "${file}.sum"
;;
*.txt)
egrep "device|Device|\(F\)" "$file" > "${file}.fail"
;;
*)
echo "Unexpected match: $file"
;;
esac
done < <(exec find "$directory" -maxdepth 1 -type f -name "*${tag}*" \( -name '*.std' -or -name '*.txt' \) -print0) ## Change or remove the maxdepth option as wanted.
Update 2
#!/bin/bash
directory=$PWD
tag=$1
echo "find: $tag on $directory"
while IFS= read -rd $'\0' file; do
echo "$file"
/projects/OPSLIB/BCMTOOLS/sumfmt_linux < "$file" > "${file}.sum"
done < <(exec find "$directory" . -maxdepth 1 -type f -name "*${tag}*" -name '*.std' -print0)
while IFS= read -rd $'\0' file; do
echo "$file"
egrep "device|Device|\(F\)" "$file" > "${file}.fail"
done < <(exec find "$directory" -maxdepth 1 -type f -name "*${tag}*" -name '*.txt' -print0)

Resources