Struggling with archiving of log files from parent and children directories - bash

I have used a few entries here to guide me through my following bash script:
LOG_FILES=$(find ~/testing.domain.net -name "domain_*log" -printf ' %P ')
NEW_LOG_FILES=$(echo $LOG_FILES | sed -e 's/\r//g')
echo ${NEW_LOG_FILES}
NOW=$(date +"%m-%d-%Y")
echo ${NOW}
tar czf ${NOW}.tar.gz ${NEW_LOG_FILES}
RC=$? # Check whether an error occured
if [[ "$RC" == "0" ]]; then
mv ${NOW}.tar.gz archivedlogs/.
rm ${LOG_FILES}
fi
The objective of the script is to find any log files in the current and sub directories and tar zipped them all before moving tar file to an archivedlogs subdirectory and then deleting log files.
When I execute the script I'm getting:
domain_info_log subdir1/domain_error_log subdir2/domain_error_log domain_error_log
12-27-2020
tar: \r\r: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors
./archiveTestLogsDaily.sh: line 12: syntax error: unexpected end of file
I can't seem to get rid of the carriage returns.
I have also tried replacing:
NEW_LOG_FILES=$(echo $LOG_FILES | sed -e 's/\r//g')
with:
NEW_LOG_FILES=${LOG_FILES//$'\r'/}
But same outcome.
Any ideas? My bash script knowledge is not the best but I'm guessing the find part comes back with carriage returns?
Also before anyone else suggests it, I don't have access to logrotate as this is a shared server.
$ uname -a
Linux 3.10.0-962.3.2.lve1.5.26.4.el7.x86_64 #1 SMP Wed Sep 11 10:29:54 EDT 2019 x86_64 x86_64 x86_64 GNU/Linux

I think it would be more safe to use a directory before making the tar archive in order to avoid a "tarbomb".
I mean something like:
now="$(date +"%m-%d-%Y")"
archiv_dir="archiv_dir-${now}"
mkdir -p "${archiv_dir}"
find ~/testing.domain.net -type f -a -iname "domain_*log" -exec mv {} "${archiv_dir}" \;
tar cvzf "${now}.tar.gz" "${archiv_dir}" && mv "${now}.tar.gz" "archivedlogs/" && rm -rf "${archiv_dir}"
P.S: make sure to double quote when you're using a variable value in Bash (or even using other POSIX shells), except inside [[.
EDIT to answer to that:
One more thing #Idriss, some of those subdirectories have the same file names, when mv tries to copy them it ignores them because they are already there... is there a way of making sure the parent directory is somehow appended to the name?
With a little bit of bash and awk, you could try something like:
#!/bin/bash
now="$(date +"%m-%d-%Y")"
archiv_dir="archiv_dir-${now}"
ROOT=~/testing.domain.net
find "${ROOT}" -type f -a -iname "domain_*log"|while read; do
subdir="$(echo $REPLY|awk -F '/' '{print $(NF-1)}')"
filename="$(basename "${REPLY}")"
[[ $subdir && $subdir != $(basename $ROOT) ]] && filename="${subdir}_${filename}"
echo mv "${REPLY}" "${archiv_dir}/${filename}"
done
Here is the output:
mv /Users/ineumann/testing.domain.net/subdir3/domain_error_log archiv_dir-12-28-2020/subdir3_domain_error_log
mv /Users/ineumann/testing.domain.net/subdir2/domain_error_log archiv_dir-12-28-2020/subdir2_domain_error_log
mv /Users/ineumann/testing.domain.net/subdir1/domain_error_log archiv_dir-12-28-2020/subdir1_domain_error_log
mv /Users/ineumann/testing.domain.net/domain_info_log archiv_dir-12-28-2020/domain_info_log
N.B: just remove the echo before the mv to perform the moves and I let you merge this example with your previous script that create directories, create the tar archive, etc.

Related

Loop through directories, check if string exists in one file, move another file

I'm working on a bash script that should do the following: for every directory beginning with Event_*, (in cat eventList), cd into the directory, and if the string "ZJ.ROT" exists in the file *.mcp, I want to copy the file "ROT" to another directory. In simpler terms: loop through directories: if string "ZJ.ROT" exists in a file in that directory, output another file from that directory to a separate directory.
#!/bin/bash
mkdir newdire
for dir in `cat eventList`; do
cd $dir
pwd
if grep "ZJ.KNYN" *.mcp; then
cp "ROT" "newdire"
fi
done
The error I get is:
./azim.sh: line 5: cd: Event_2014.11.21.10.10.19.630: No such file or directory
/Users/files/Event_2013.12.01.06.29.57.800
grep: *.mcp: No such file or directory
For some reason, this for loop isn't looping through each directory, but it's stuck in the first directory Event_2013.... Any ideas about how to implement this code?
After the first time you cd to a subdirectory you are in it for all future loop iterations so your subsequent cds will fail, as you are experiencing. You also need to quote your variables and there's other issues. Try this:
pwd="$PWD"
mkdir newdire
while IFS= read -r dir; do
cd "$dir"
grep -Fq "ZJ.KNYN" *.mcp &&
cp "ROT" "${pwd}/newdire"
cd "$pwd"
done < eventList
but of course you don't actually need to cd:
mkdir newdire
while IFS= read -r dir; do
grep -Fq "ZJ.KNYN" "$dir"/*.mcp &&
cp "${dir}/ROT" newdire
done < eventList
Problem seems to be here:
if grep "ZJ.KNYN" *.mcp; then
You should use -q option in grep to suppress the output and check the return status like this:
if grep -qF "ZJ.KNYN" *.mcp; then
-F is for fixed string search.
Also there is no need to change directory inside the loop.
Your full script can be better rewritten as:
#!/bin/bash
mkdir newdire
for dir in Event_*; do
if [[ -d "$dir" ]] && grep -qF "ZJ.KNYN" "$dir"/*.mcp 2>/dev/null; then
cp "$dir/ROT" "newdire/"
fi
done

Collapse nested directories in bash

Often after unzipping a file I end up with a directory containing nothing but another directory (e.g., mkdir foo; cd foo; tar xzf ~/bar.tgz may produce nothing but a bar directory in foo). I wanted to write a script to collapse that down to a single directory, but if there are dot files in the nested directory it complicates things a bit.
Here's a naive implementation:
mv -i $1/* $1/.* .
rmdir $1
The only problem here is that it'll also try to move . and .. and ask overwrite ./.? (y/n [n]). I can get around this by checking each file in turn:
IFS=$'\n'
for file in $1/* $1/.*; do
if [ "$file" != "$1/." ] && [ "$file" != "$1/.." ]; then
mv -i $file .
fi
done
rmdir $1
But this seems like an inelegant workaround. I tried a cleaner method using find:
for file in $(find $1); do
mv -i $file .
done
rmdir $1
But find $1 will also give $1 as a result, which gives an error of mv: bar and ./bar are identical.
While the second method seems to work, is there a better way to achieve this?
Turn on the dotglob shell option, which allows the your pattern to match files beginning with ..
shopt -s dotglob
mv -i "$1"/* .
rmdir "$1"
First, consider that many tar implementations provide a --strip-components option that allows you to strip off that first path. Not sure if there is a first path?
tar -tf yourball.tar | awk -F/ '!s[$1]++{print$1}'
will show you all the first-level contents. If there is only that one directory, then
tar --strip-components=1 -tf yourball.tar
will extract the contents of that directory in tar into the current directory.
So that's how you can avoid the problem altogether. But it's also a solution to your immediate problem. Having extracted the files already, so you have
foo/bar/stuff
foo/bar/.otherstuff
you can do
tar -cf- foo | tar --strip-components=2 -C final_destination -xf-
The --strip-components feature is not part of the POSIX specification for tar, but it is on both the common GNU and OSX/BSD implementations.

How to move folders of certain size in command line?

In my external HDD I have two partitions, one is for Mac and the other for Windows (FAT32). Since my Mac partition is almost full due to Time Machine backup, I want to move some of my old folders (in which are movies) from the Mac partition to the Windows partition. However, the FAT32 file system only allows each file less than 4GB. But my some of the folders contain files larger than 4G. I don't want to manually go through each folder , check the size and then copy & paste the folders of small size.
So my question is:
What is the command for moving all the folders (including the sub-directories) less than 4GB to the new partition? Does it have anything to do with the options of mv command?
Thanks
--- Update 12/7/2014---
I ran find . -mindepth 1 -type d -exec bash -c 'f="$1";set $(du -bs "$f"); \ [[ $1 -lt 4294967296 ]] && echo mv "$f" /dest-dir' - '{}' \; >> output.txt.
The following was the first a few lines of my output:
Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.stZBByQJc0/Render
BASH=/bin/bash
BASH_ARGC=([0]="1")
BASH_ARGV=([0]=".")
BASH_EXECUTION_STRING=$'f="$1";set $(du -bs "$f"); \\\n [[ $1 -lt 4294967296 ]] && echo mv "$f" /Volumes/WIN_PANC/movies/'
BASH_LINENO=()
BASH_SOURCE=()
BASH_VERSINFO=([0]="3" [1]="2" [2]="53" [3]="1" [4]="release" [5]="x86_64-apple-darwin14")
BASH_VERSION='3.2.53(1)-release'
CLICOLOR=1
COLORFGBG='15;0'
They are not the folders I want to move. Am I doing right?
You can use this find command to list directories that have files greater than 4GB:
find . -mindepth 1 -type d -exec bash -c 'f="$1"; read s _ < <(du -s "$f"); \
[[ $s -lt 4194304 ]] && echo mv "$f" /dest-dir' - '{}' \;
Remove echo before mv command once you're satisfied with the listing.
Using the following codes can do this for you (for files >4G
#! /bin/bash
my_files=`ls --almost-all -1v -s -A --block-size=G|sort|sed -e 's#^[0-4]*G##g' -e '$ s#.*##g'`
echo "$my_files" >> my_files.txt
while read -r file; do
echo "MOVING FILE : $file"
mv "$file" "destination_location"
sleep 0.5
done < my_files.txt
rm -rf my_files.txt
Note: change your directory to where all your files to be copied are present in a terminal, then you can run script from the same terminal. Ensure you replace "destination_location" with the directory you want to move the file to inside the codes. Afterwards execute script.
Note: You will have to change your directory and run the codes in each directory.

how to get basename in -exec of find?

I cannot get the following piece of script (which is part of a larger backup script) to work correctly:
BACKUPDIR=/BACKUP/db01/physical/incremental # Backups base directory
FULLBACKUPDIR=$BACKUPDIR/full # Full backups directory
INCRBACKUPDIR=$BACKUPDIR/incr # Incremental backups directory
KEEP=5 # Number of full backups (and its incrementals) to keep
...
FIRST_DELETE=`expr $KEEP + 1` # add one to the number of backups to keep, this will be the first deleted
FILE0=`ls -ltr $FULLBACKUPDIR | awk '{print $9}' | tail -$FIRST_DELETE | head -1` # search for the first backup to be deleted
...
find $FULLBACKUPDIR -maxdepth 1 -type d ! -newer $FULLBACKUPDIR/$FILE0 -execdir echo "removing: "$FULLBACKUPDIR/$(basename {}) \; -execdir bash -c 'rm -rf $FULLBACKUPDIR/$(basename {})' \; -execdir echo "removing: "$INCRBACKUPDIR/$(basename {}) \; -execdir bash -c 'rm -rf $INCRBACKUPDIR/$(basename {})' \;
So the find works correctly which on its own will output something like this:
/BACKUPS/db01/physical/incremental/full/2013-08-12_17-51-28
/BACKUPS/db01/physical/incremental/full/2013-08-12_17-51-28
/BACKUPS/db01/physical/incremental/full/2013-08-12_17-25-07
What I want is the -exec to echo a line showing what is being removed and then remove the folder from both directories.
I've tried various ways to get just the basename but nothing seems to be working. I get this:
removing: /BACKUPS/mysql/physical/incremental/full/"/BACKUPS/mysql/physical/incremental/full/2013-08-12_17-51-28"
removing: /BACKUPS/mysql/physical/incremental/incr/"/BACKUPS/mysql/physical/incremental/full/2013-08-12_17-51-28"
removing: /BACKUPS/mysql/physical/incremental/full/"/BACKUPS/mysql/physical/incremental/full/2013-08-12_17-25-07"
And of course the folders arn't deleted because they don't exist, just fail silently because of the -f option. If I remove the -f I get the 'cannot be found' error on each rm.
How do I accomplish this? Because backups and parts of backups may be stored across different storage systems I really need the ability to just get the folder name for use in any known path.
the $(basename {}) is run first, making removing: "$INCRBACKUPDIR/$(basename {}) to removing: "$INCRBACKUPDIR/{} then the replacement is done of {}.
a way around it may be to pipe it to bash:
-exec echo "echo \"removing: \\\"$INCRBACKUPDIR/\$(basename {})\\\"\" | bash" \;
Lots of broken here.
All caps variables are by convention env vars and should not be used in scripts.
Using legacy backticks instead of $()
Parsing the output of ls (!)
Parsing the output of ls -l (!!!)
Expanding variables known to contain paths without full quotes.
All you absolutely need in order to improve this is to -exec bash properly, e.g.
-execdir bash -c 'filepath="$1" ; base=$(basename "$filepath") ; echo use $filepath and $base here' -- {} \;
But how about this instead:
#!/usr/bin/env bash
backup_base=/BACKUP/db01/physical/incremental
full_backup="$backup_base"/full
incremental_backup="$backup_base"/incr
keep=5
rm=echo
let n=0
while IFS= read -r -d $'\0' line ; do
file="${line#* }"
if [[ $n -lt $keep ]] ; then
let n=n+1
continue
fi
base=$(basename "$file")
echo "removing: $full_backup/$base"
"$rm" -rf -- "$full_backup"/"$base"
echo "removing: $incremental_backup/$base"
"$rm" -rf -- "$incremental_backup"/"$base"
done < <(find "$full_backup" -maxdepth 1 -printf '%T#.%p\0' 2>/dev/null | sort -z -r -n -t. -k1,2)
Iterate over files and directories immediately under the backup dir and skip the first 5 newest. Delete from the full and incremental dirs files matching the names of the rest.
This is an essentially safe version, except of course for timing attacks.
I have defined rm as being echo to avoid accidental deletes; swap it back to rm for actual deletion once you're sure it's correct.

Deleting all directories in a given folder in bash

I am trying to write a shell script to delete all the sub directories in a given directory. I know there is an easy approach for the same.
Like doing this
find ./ -type d -exec rm -r {} \;
but since I am learning shell scripting so I prefer to write a script for the same. Here is my approach
for i in `ls ./*`; do
if [ -d $i ];then
rm -r $i
fi
done
When I run this script this gives me following errors
rm: cannot remove directory: `.'
after giving this error this stops.So what is the error in my approach.As far as I understand blank names should create some problem. But this script has failed to go that far.
The ls ./* makes a list of all the files in each immediate subdir of . The -d then checks the name of the file but as if it was in . not the subdir it comes from.
For example if you had:
foo1/
bar
foo2/
baz
then ls ./* would make a list of bar and baz, as the ./* would match foo1 and foo2 and ls would then list the contents of each of those 2 directories.
The error message you are getting is probably because your ls has been aliased to be 'ls -a' which lists . and .. As the answer by Florin says, you can use ls -A ./* to avoid that issue.
If you just want to delete the directories in ., just do:
for i in `ls -A`; do if [ -d $i ]; then rm -r $i; fi; done
. and .. should not appear in the output of ls.
However, you can test with
ls -A
(-A means 'almost all'
-a means 'all')
And: why you don't jus use for i in 'ls' ?
You shouldn't use ls in this command. There's a very simple way to make sure you only iterate over directories:
for dir in */
do
echo "$dir"
done
The problem is that ls command list links for the current folder and the parent folder. So you must test if your i variable is setted to '.' or '..'
if [ -d $i ] -a [ $i != '.' ] -a [ $i != '..' ]
then
rm -r $i
fi
"*/" should match all subdirectories
Try something like this:
rm -rf /path/to/mySubfolder/*/

Resources