iterate over all files in directory - bash

I'm trying to write a BASH script that iterate over all files in directory and if the file is not zip than zip it
#!/bin/bash
echo "Start"
for f in *
do
echo "Start loop $f "
# if .bak backup file exists, read next file
if [ -${f} = *.zip ]
then
echo "Skiping $f file..."
continue # read next file and skip the cp command
fi
zip $f.zip $f
done
echo "Closing"
I have the following files in the directory: 1, 2, 3.zip, zipper.sh
the output of the script over the directory needs to be
1, 1.zip, 2, 2.zip, 3.zip, zipper.sh, zipper.sh.zip
but it also creating 3.zip.zip

Try this below code.
#!/bin/bash
for f in `find . -type f -not -name "*.zip"`;
do
#echo $f
zip "$f".zip $f
done

Related

How do I rename and overwrite part of file name in Bash at the same time?

I have multiple JS files in folder, for example, foo.js, this_is_foo.js, foo_is_function.js.
I want to append a number which is passed as parameter in front of extension ".js", such as foo.1.js, this_is_foo.1.js, foo_is_function.1.js
I write a script to append a number successfully the first time, but if I run the script twice, it does not overwrite the first number but append right after that.
Actual result: foo.js --> foo.1.js (1st run) --> foo.1.2.js (2nd run).
Expected result: foo.js --> foo.1.js (1st run) --> foo.2.js (2nd run).
This is my script:
#!/bin/sh
param=$1
for file in *.js; do
ext="${file##*.}";
filename="${file%.*}";
mv "$file" "${filename}.${param}.${ext}";
done
How can I do that? I want to write pure bash script, not to use any tools.
Before doing the rename you can check if what is after the last dot in filename (${filename%.*}) is numeric. If so switch that with param instead of appending a new param
Since you are writing that you want to use pure bash I assume that it is ok to change the shebang to #!/bin/bash:
#!/bin/bash
param=$1
for file in *.js; do
ext="${file##*.}";
filename="${file%.*}";
# Check if what is after the last dot in filename is numeric
# Then assume that it should be switched to param
if [[ ${filename##*.} =~ ^[0-9]+$ ]]; then
mv "$file" "${filename%.*}.${param}.${ext}"
## Else add param
else
mv "$file" "${filename}.${param}.${ext}"
fi
done
Testrun
$> touch a.js && find -type f -name *.js && \
./test.sh 1 && find -type f -name *.js && \
./test.sh 2 && find -type f -name *.js
./a.js
./a.1.js
./a.2.js
You can use extended globbing to match an optional . (the one before the number) and any amount of digits before the .js suffix.
I also added the . to the $param variable if the variable is non-empty. This way you can call the script without parameter and .<number> is removed instead of added/changed.
#!/bin/bash
shopt -s extglob # enable extended globbing
param=$1
if [ -n "$param" ]; then
param=.${param} # add prefix `.`
fi
for file in *.js; do
newfile=${file%%?(.)*([0-9]).js}${param}.js
if [ "$file" = "$newfile" ]; then
echo "skipping \"${file}\", no need to rename"
elif [ -f "$newfile" ]; then
echo "skipping \"$file\", file \"$newfile\" already exists"
else
mv "$file" "$newfile"
fi
done
Usage:
./script.sh 1 # change suffix to `.1.js`
./script.sh 2 # change suffix to `.2.js`
./script.sh # change suffix to `.js`
Posix compliant solution, which allows you to keep using /bin/sh
#!/bin/sh
param=$1
for file in *.js; do
ext="${file##*.}";
filename="${file%.*}";
[ -z "${filename##*.##*[!0-9]*}" ] && mv "$file" "${filename}.${param}.${ext}" || mv "$file" "${filename%.*}.${param}.${ext}"
done
Testrun
$> touch a.js && find -type f -name *.js && \
./test.sh 1 && find -type f -name *.js && \
./test.sh 2 && find -type f -name *.js
./a.js
./a.1.js
./a.2.js
This might do what you want.
#!/usr/bin/env bash
counter=1
for file in *.js; do
if [[ $file =~ (^[^\.]+)(\.js)$ ]]; then
mv -v "$file" "${BASH_REMATCH[1]}.$counter${BASH_REMATCH[2]}"
elif [[ $file =~ (^[^\.]+)\.([[:digit:]]+)(\.js)$ ]]; then
first=${BASH_REMATCH[1]}
counter=${BASH_REMATCH[2]}
extension=${BASH_REMATCH[3]}
((counter++))
mv -v "$file" "$first.$counter$extension"
fi
done
In action.
mkdir -p /tmp/123 && cd /tmp/123
touch foo.js
touch this_is_foo.js
touch this_is_foucntion.js
First run
./myscript
Output
renamed 'foo.js' -> 'foo.1.js'
renamed 'this_is_foo.js' -> 'this_is_foo.1.js'
renamed 'this_is_foucntion.js' -> 'this_is_foucntion.1.js'
Second run.
renamed 'foo.1.js' -> 'foo.2.js'
renamed 'this_is_foo.1.js' -> 'this_is_foo.2.js'
renamed 'this_is_foucntion.1.js' -> 'this_is_foucntion.2.js'
Third run.
renamed 'foo.2.js' -> 'foo.3.js'
renamed 'this_is_foo.2.js' -> 'this_is_foo.3.js'
renamed 'this_is_foucntion.2.js' -> 'this_is_foucntion.3.js'
Using a for loop
for i in {1..5}; do ./script.sh ; done
Output
renamed 'foo.js' -> 'foo.1.js'
renamed 'this_is_foo.js' -> 'this_is_foo.1.js'
renamed 'this_is_foucntion.js' -> 'this_is_foucntion.1.js'
renamed 'foo.1.js' -> 'foo.2.js'
renamed 'this_is_foo.1.js' -> 'this_is_foo.2.js'
renamed 'this_is_foucntion.1.js' -> 'this_is_foucntion.2.js'
renamed 'foo.2.js' -> 'foo.3.js'
renamed 'this_is_foo.2.js' -> 'this_is_foo.3.js'
renamed 'this_is_foucntion.2.js' -> 'this_is_foucntion.3.js'
renamed 'foo.3.js' -> 'foo.4.js'
renamed 'this_is_foo.3.js' -> 'this_is_foo.4.js'
renamed 'this_is_foucntion.3.js' -> 'this_is_foucntion.4.js'
renamed 'foo.4.js' -> 'foo.5.js'
renamed 'this_is_foo.4.js' -> 'this_is_foo.5.js'
renamed 'this_is_foucntion.4.js' -> 'this_is_foucntion.5.js'

Bash script overwriting existing .tar.gz file

I have written a small bash file to backup a repository. If the archive file doesn't exists, it should be created. Thereafter, files found should be appended to the existing file.
For some reason, the archive keeps getting (re)created/overwritten. This is my script below. Can anyone see where the logic error is coming from?
#!/bin/bash
REPOSITORY_DIR=$PWD/../repository
STORAGE_DAYS=30
#https://stackoverflow.com/questions/48585148/reading-names-of-imediate-child-folders-into-an-array-and-iterating-over-it?noredirect=1#comment84167181_48585148
while IFS= read -rd '' file;
do fbname=$(basename "$file");
# Find all persisted (.CSV) files that are older than $STORAGE_DAYS
files_to_process=($(find $REPOSITORY_DIR/$fbname -type f -name '*.csv' -mtime +$STORAGE_DAYS))
backup_datafile=$REPOSITORY_DIR/$fbname/$fbname.data.tar.gz
#echo $backup_datafile
# If the tar.gz file does not exist, we want to create it
# else (i.e. file exists), we want to add files to it
# Solution from: https://stackoverflow.com/questions/28185012/how-to-create-tar-for-files-older-than-7-days-using-linux-shell-scripting
NUM_FILES_FOUND=${#files_to_process[#]}
if [ $NUM_FILES_FOUND -gt 0 ]; then
echo "Found ${#files_to_process[#]} files to process ..."
if [ ! -f backup_datafile ]; then
# Creating a new tar.gz file, since file was not found
echo "Creating new backup file: $backup_datafile"
tar cvfz $backup_datafile "${files_to_process[#]}"
else
echo "Adding files to existing backup file: $backup_datafile"
# https://unix.stackexchange.com/questions/13093/add-update-a-file-to-an-existing-tar-gz-archive
gzip -dc $backup_datafile | tar -r "${files_to_process[#]}" | gzip >$backup_datafile.new
mv $backup_datafile.new $backup_datafile
fi
# remove processed files
for filename in "${files_to_process[#]}"; do
rm -f "$filename"
done
else
echo "Skipping directory: $REPOSITORY_DIR/$fbname/. No files found"
fi
done < <(find "$REPOSITORY_DIR" -maxdepth 1 -mindepth 1 -type d -print0)
This is where it went wrong:
if [ ! -f backup_datafile ]; then
I guess it should have been
if [ ! -f $backup_datafile ]; then
^
Or better yet, put that in quotes:
if [ ! -f "$backup_datafile" ]; then
if [ ! -f backup_datafile ]; then
i think you might be missing a "$" there

move command performs move in source directory too

I have written a shell script to move files from source directory to destination directory.
/home/tmp/ to /home/from/
The move happens correctly but it displays message
mv: /home/tmp/testfile_retry_17072017.TIF
/home/tmp/testfile_retry_17072017.TIF are identical.
and if source directory is empty it displays
mv: cannot rename /home/tmp/* to /home/from/*
for file in /home/tmp/*
if [ -f "$file" ]
then
do
DIRPATH=$(dirname "${file}")
FILENAME=$(basename "${file}")
# echo "Dirpath = ${DIRPATH} Filename = ${FILENAME}"
mv "${DIRPATH}/"${FILENAME} /home/from
echo ${FILENAME} " moved to from directory"
done
else
echo "Directory is empty"
fi
You should use find instead of /home/tmp/* as shown.
for file in $(find /home/tmp/ -type f)
do
if [ -f "$file" ]
then
DIRPATH=$(dirname "${file}")
FILENAME=$(basename "${file}")
# echo "Dirpath = ${DIRPATH} Filename = ${FILENAME}"
mv "${DIRPATH}/"${FILENAME} /home/from
echo ${FILENAME} " moved to from directory"
else
echo "Directory is empty"
fi
done
You have things a bit out of order with:
for file in /home/tmp/*
if [ -f "$file" ]
then
do
Of course "$file" will exist -- you are looping for file in /home/tmp/*. It looks like you intended
for file in /home/tmp/*
do
FILENAME=$(basename "${file}")
if [ ! -f "/home/from/$FILENAME" ] ## if it doesn't already exist in dest
then
Note: POSIX shell include parameter expansions that allow you to avoid calling dirname and basename. Instead you can simply use "${file##*/}" for the filename (which just says remove everything from the left up to (and including) the last /). That is the only expansion you need (as you already know the destination directory name). This allows you to check [ -f "$dest/${f##*/}" ] to determine if a file with the same name you are moving already exists in /home/from
You could use that to your advantage with:
src=/home/tmp ## source dir
dst=/home/from ## destination dir
for f in "$src"/* ## for each file in src
do
[ "$f" = "$src/*" ] && break ## src is empty
if [ -f "$dst/${f##*/}" ] ## test if it already exists in dst
then
printf "file '%s' exists in '%s' - forcing mv.\n" "${f##*/}" "$dst"
mv -f "$f" "$dst" ## use -f to overwrite existing
else
mv "$f" "$dst" ## regular move otherwise
fi
done
There is a great resource for checking your shell code called ShellCheck.net. Just type your code into the webpage (or paste it) and it will analyze your logic and variable use and let you know where problem are identified.
Look things over and let me know if you have further questions.

bash script - how to check if any directory that starts with exists

I've been trying to write a script:
EFIDIR=/Volumes/EFI
KEXTDEST=$EFIDIR/EFI/CLOVER/kexts/Other
if [[ -d $EFIDIR/EFI/CLOVER/kexts/10.* ]]; then
echo "Directory found."
if [[ -d $EFIDIR/EFI/CLOVER/kexts/10.*/*.kext ]]; then
echo "Kext(s) found."
cp -R $EFIDIR/EFI/CLOVER/kexts/10.*/*.kext $KEXTDEST
fi
rm -R $EFIDIR/EFI/CLOVER/kexts/10.*
fi
I want to check whether any folder that starts with "10." (can be 10.10, 10.11... etc) exist then if any of those folders contains a folder that ends with (.kext) exists... copy to the destination folder.
How to write it the right way?
Thanks.
Try this:
EFIDIR=/Volumes/EFI
KEXTDEST=$EFIDIR/EFI/CLOVER/kexts/Other
for each in $(find $EFIDIR/EFI/CLOVER/kexts/ -name "10.*" -type d); do
echo "Directory found."
for innerdir in $(find $each -name "*.kext" -type d); do
echo "Kext(s) found."
cp -R $innerdir $KEXTDEST
done
rm -R $each
done
find $EFIDIR/EFI/CLOVER/kexts/ -name "10.*" -type d will look for directories (-type d) starting with name 10.* in $EFIDIR/EFI/CLOVER/kexts and if found will loop through every one in the for loop.
The inner for loop looks for directories starting with name *.kext .

BASH user drive selection

I am creating a simple script for mac os x to provide a user with a list of available drives to backup from based on the contents of /Volumes, but I am running into an issue with handling the output of the 'find' command if the drive name contains a space. The find command outputs each drive on a separate line, but the 'for each' breaks the name into parts. Example:
Script:
#!/bin/bash
find /Volumes -maxdepth 1 -type d
echo ""
i=1
for Output in $(find /Volumes -maxdepth 1 -type d)
do
DriveChoice[$i]=$Output
echo $i"="${DriveChoice[$i]}
i=$(( i+1 ))
done
Output:
/Volumes
/Volumes/backup
/Volumes/EZBACKUP DRIVE
/Volumes/Tech
1=/Volumes
2=/Volumes/backup
3=/Volumes/EZBACKUP
4=DRIVE
5=/Volumes/Tech
logout
[Process completed]
This seems like it should be fairly straight-forward. Is there a better way for me to accomplish this?
Update: Thank you chepner, that works perfectly. It is a simple script to generate a ditto command, but I will post it here anyway in case someone finds any part of it useful:
#!/bin/bash
#Get admin rights
sudo -l -U administrator bash
#Set the path to the backup drive
BackupPath="/Volumes/backup/"
#Generate a list of source drives, limiting out invalid options
i=1
while read -r Output; do
if [ "$Output" != "/Volumes" ] && [ "$Output" != "/Volumes/backup" ] && [ "$Output" != "/Volumes/Tech" ] ; then
DriveChoice[$i]=$Output
echo "$i=${DriveChoice[$i]}"
i=$(( i+1 ))
fi
done < <( find /Volumes -maxdepth 1 -type d)
#Have the user select from valid drives
echo "Source Drive Number?"
read DriveNumber
#Ensure the user input is in range
if [ $DriveNumber -lt $i ] && [ $DriveNumber -gt 0 ]; then
Source=${DriveChoice[$DriveNumber]}"/"
#Get the user's NetID for generating the folder structure
echo "User's NetID?"
read NetID
NetID=$NetID
#Grab today's date for generating folder structure
Today=$(date +"%m_%d_%Y")
#Destination for the Logfile
Destination=$BackupPath$NetID"_"$Today"/"
#Full path for the LogFile
LogFile=$Destination$NetID"_log.txt"
mkdir -p $Destination
touch $LogFile
#Destination for the backup
Destination=$Destination"ditto/"
#Execute the command
echo "Processing..."
sudo ditto "$Source" "$Destination" 2>&1 | tee "$LogFile"
else
#Fail if the drive selection was out of range
echo "Drive selection error!"
fi
You cannot safely iterate over the output of find using a for loop, because of the space issue you are seeing. Use a while loop with the read built-in instead:
#!/bin/bash
find /Volumes -maxdepth 1 -type d
echo ""
i=1
while read -r output; do
DriveChoice[$i]=$output
echo "$i=${DriveChoice[$i]}"
i=$(( i+1 ))
done < <( find /Volumes -maxdepth 1 -type d)

Resources