Error on my code - bash

#!/bin/bash
# When a match is not found, just present nothing.
shopt -s nullglob
files=(*.wav)
if [[ ${#files[#]} -eq 0 ]]; then
echo "No match found."
fi
for file in "${files[#]}"; do
# We get the date part
find_date=$(stat -c %y $file | awk '{print $1}')`
for t in "${parts[#]}"; do
IFS="-." read -ra parts <<< "$file"
if [[ $t == [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] ]]; then
file_date=$t
break
fi
done
# If a value was not assigned, then show an error message and continue to the next file.
# Just making sure there is nothing in Array and date before it moves on
if [[ -z $file_date ]]; then
continue
fi
file_year=${file_date:0:4}
file_month=${file_date:6:2}
mkdir -p "$file_year/$file_month"
# -- is just there to not interpret filenames starting with - as options.
echo "Moving: ./"$file "to: " "./"$file_year"/"$file_month
mv "$file" "$file_year/$file_month"
done
I have some files that are .wav.... I want to put the files in an array like I did and then Stat -c %y filename |awk $1 which gives me YYYY-MM-DD and then I wanna put the date in the array so then I can set it 2 variables Year and Month so then I can either make a DIR Year/Month or if DIR is already there then just mv it. which is mkdir -p... Geting errors in my code but I do not think i am reading the file in my array correct.
25: continue: only meaningful in a for',while', or `until' loop
my echo statement Moving: ./OUT117-20092025-5845.wav to: .//

Besides some syntax issues main problem is that you cannot have continue outside for loop.
Syntax error are:
You cannot have space on either side of assignment operator = so find_date= stat -c %y $file | awk{print $1}` should befind_date=$(stat -c %y $file | awk '{print $1}')`
Regex operator is =~ instead of ==
UPDATE: You are starting your for loop before you are setting your variable.
for t in "${parts[#]}"; do
IFS="-." read -ra parts <<< "$file"
It should be:
IFS="-." read -ra parts <<< "$file"
for t in "${parts[#]}"; do

Related

Copy last modified binary file over the other one

I would like to compare two binary files (very small, 100Kb each) and replace the oldest with the last modified one.
I have created a simple script, but I would need your help to make it running properly:
#!/bin/sh
# select the two files
FILE1="/dir1/file1.binary"
FILE2="/dir2/file2.binary"
# create the hash of the two files
HASH1="$(md5sum $FILE1 | cut -c 1-32)"
HASH2="$(md5sum $FILE2 | cut -c 1-32)"
# compare the two hashes
if [ "$HASH1" == "$HASH2" ];
# if the two hashes are the same, exit
then
echo "the two files are identical"
exit 0
# otherwise compare which of them has been last modified
fi
DATE1="(stat -c %Y $FILE1)"
DATE2="(stat -c %Y $FILE2)"
# if FILE1 is newer than FILE2, replace FILE2 with FILE1
if [ "${DATE1}" -gt "${DATE2}" ];
then
cp $FILE1 $FILE2
echo "${FILE2} was replaced by ${FILE1}"
# if FILE2 is newer than FILE1, replace FILE1 with FILE2
fi
cp $FILE2 $FILE1
echo "${FILE1} was replaced by ${FILE2}"
exit 0
The file seems working (at least if the two files are identical), but if one file has been modified, I receive the following error:
line 24: [: {(stat -c %Y test1)}: integer expression expected
What is wrong?
By the way, is there a better way to solve this problem?
Thanks
Thank you so much everybody for your help. Here is how the script looks like now. There is also notification on QTS for QNAP, but it can be taken out if running elsewhere or not needed.
#!/bin/sh
# select the two files
FILE1="/dir1/file1"
FILE2="/dir2/file2"
# use or create a log file with timestamp of the output
LOG="/dir1/ScriptLog.txt"
TIMESTAMP=$(date +"%Y-%m-%d %Hh:%M")
if [ ! -e $LOG ]; then
touch $LOG
echo "$TIMESTAMP - INFO: '$LOG' does not exists but has been created." >&2
# else
# echo "$TIMESTAMP - INFO: '$LOG' exists and it will be used if any change to '$FILE1'
# or to '$FILE2' is needed." >&2
fi
# You can also pass the two file names as arguments for the script
if [[ $# == 2 ]]; then
FILE1=$1
FILE2=$2
fi
# check if the two files exist and are regular
if [ -f "$FILE1" -a -f "$FILE2" ]; then
# meanwhile compare FILE1 against FILE2
# if files are identical, stop there
if cmp "$FILE1" "$FILE2" 2>/dev/null>/dev/null; then
echo "$TIMESTAMP - INFO: '$FILE1' and '$FILE2' are identical." >&2 | >> $LOG
# if FILE1 is newer than FILE2, copy FILE1 over FILE2
elif [ "$FILE1" -nt "$FILE2" ]; then
if cp -p "$FILE1" "$FILE2"; then
echo "$TIMESTAMP - INFO: '$FILE1' replaced '$FILE2'." >&2 | >> $LOG
# if copy is successful, notify it into QTS
/sbin/notice_log_tool -a "$TIMESTAMP - INFO: '$FILE1' replaced '$FILE2'." --severity=5 >&2
else
echo "$TIMESTAMP - ERROR: FAILED to replace '$FILE2' with '$FILE1'." >&2 | >> $LOG
exit 1
fi
# if FILE1 is older than FILE2, copy FILE2 over FILE1
elif [ "$FILE1" -ot "$FILE2" ]; then
if cp -p "$FILE2" "$FILE1"; then
echo "$TIMESTAMP - INFO: '$FILE2' replaced '$FILE1'." >&2 | >> $LOG
# if copy is successful, notify it into QTS
/sbin/notice_log_tool -a "$TIMESTAMP - INFO: '$FILE2' replaced '$FILE1'." --severity=5 >&2
else
echo "$TIMESTAMP - ERROR: FAILED to replace '$FILE2' with '$FILE1'." >&2 | >> $LOG
exit 1
fi
# if two files are not identical but with same modification date
else
echo "$TIMESTAMP - ERROR: We should never reach this point. Something is wrong in the script." >&2 | >> $LOG
exit 1
fi
# if one file does not exist or is not valid, exit
else
echo "$TIMESTAMP - ERROR: One of the files does not exist, has been moved or renamed." >&2 | >> $LOG
# if error, notify it into QTS
/sbin/notice_log_tool -a "$TIMESTAMP - ERROR: One of the files does not exist, has been moved or renamed." --severity=5 >&2
exit 1
fi
I'm also going to suggest refactoring this, both to simplify the code, and to save your CPU cycles.
#!/bin/sh
# If both files exist....
if [ -f "$1" -a -f "$2" ]; then
# If they have the same content...
if cmp "$1" "$2" >/dev/null 2>/dev/null; then
echo "INFO: These two files are identical." >&2
# If one is newer than the other...
elif [ "$1" -nt "$2" ]; then
if cp -p "$1" "$2"; then
echo "INFO: Replaced file '$2' with '$1'." >&2
else
echo "ERROR: FAILED to replace file." >&2
exit 1
fi
# If the other is newer than the one...
elif [ "$1" -ot "$2" ]; then
if cp -p "$2" "$1"; then
echo "INFO: Replaced file '$1' with '$2'." >&2
else
echo "ERROR: FAILED to replace file." >&2
exit 1
fi
else
echo "ERROR: we should never reach this point. Something is wrong." >&2
exit 1
fi
else
echo "ERROR: One of these files does not exist." >&2
exit 1
fi
A few things that you may find useful.
This avoids calculating an md5 on each of the files. While comparing sums may be fine for small files like yours, it gets mighty expensive as your files grow. And it's completely unnecessary, because you have the cmp command available. Better to get in the habit of writing code that will work with less modification when you recycle it for the next project.
An if statement runs a command, usually [ or [[, but it can be any command. Here, we're running cmp and cp within an if, so that we can easily check the results.
This doesn't use stat anymore. While it's possible that you may never look beyond Linux, it's always a good idea to keep portability in mind, and if you can make your script portable, that's great.
This is not a bash script. Neither was your script -- if you call your script with /bin/sh, then you're in POSIX compatibility mode, which already makes this more portable than you thought. :-)
Indenting helps. You might want to adopt it for your own scripts, so that you can have a better visual idea of what commands are associated with the various conditions that are being tested.
What about something a bit simpler like the following?
#!/bin/sh
# select the two files from cli
# $1 = current file
# $2 = new file
FILE1=$1
FILE2=$2
# otherwise compare which of them has been last modified
DATE1=`(stat -c %Y $FILE1)`
DATE2=`(stat -c %Y $FILE2)`
if [ $DATE2 -gt $DATE1 ]; then
echo "cp -f $FILE2 $FILE1"
# cp -f $FILE2 $FILE1
fi
Almost there. Cleaning up your code and tweaking it a bit here is what I got
#!/bin/bash
# select the two files (default option)
FILE1="/dir1/file1.binary"
FILE2="/dir1/file2.binary"
# You can also pass the two file names as arguments for the script
if [ $# -eq 2 ]; then
FILE1=$1
FILE2=$2
fi
# create the hash of the two files
HASH1="$(md5sum $FILE1 | sed -n -e 's/^.*= //p')"
HASH2="$(md5sum $FILE2 | sed -n -e 's/^.*= //p')"
# get the dates of last modification
DATE1="$(stat -f '%m%t%Sm' $FILE1 | cut -c 1-10)"
DATE2="$(stat -f '%m%t%Sm' $FILE2 | cut -c 1-10)"
# Uncomment to see the values
#echo $FILE1 ' = hash: ' $HASH1 ' date: ' $DATE1
#echo $FILE2 ' = hash: ' $HASH2 ' date: ' $DATE2
# compare the two hashes
if [ $HASH1 == $HASH2 ]; then
# if the two hashes are the same, exit
echo "the two files are identical"
exit 0
fi
# compare the dates
if [ $DATE1 -gt $DATE2 ]; then
# if FILE1 is newer than FILE2, replace FILE2 with FILE1
cp $FILE1 $FILE2
echo "${FILE2} was replaced by ${FILE1}"
elif [ $DATE1 -lt $DATE2 ]; then
# else if FILE2 is newer than FILE1, replace FILE1 with FILE2
cp $FILE2 $FILE1
echo "${FILE1} was replaced by ${FILE2}"
else
# else the files are identical
echo "the two files are identical"
fi
Your way of getting the date was wrong, at least on my machine. So I rewrote it.
Your hash string was wrong. You were effectively cropping the string to the first 32 characters. By using sed you can actually get rid of the first part of the command and simply store the result of the md5sum.
You also misused the conditional statements as HuStmpHrrr pointed out.
The rest is cosmetics.

How do I manipulate filenames in bash?

I have a bunch of images that I need to rename, so I can use them and I was wondering how to do this.
The way they need to be is that first 5 will be kept and then for the 6th I would write a number from 1-3. I only know that the first 5 are static; on pics belonging to same "family" and can be used for comparison and the 6th char is not known.
Example:
12345random.jpg
12345randomer.jpg
0987654more_random.jpg
09876awesome.jpg
09876awesomer.jpg
09876awesomest.jpg
09876soawesomegalaxiesexplode.jpg
would become.
12345.jpg
123452.jpg
09876.jpg
098761.jpg
098762.jpg
It would be cool if it would only handle the loop so that 3 pics could be only renamed and rest skipped.
I found some stuff on removing letters to certain point, but nothing that use, since I am quite poor at bash scripting.
Here is my approach, but it kind of sucks, since I tried modifying scripts I found, but the idea is there
//I could not figure how to remove the chars after 5th not the other way around
for file in .....*; do echo mv $file `echo $file | cut -c6-`; done
done
//problem also is that once the names conflict it produces only 1 file named 12345.jpg 2nd one will not be created
//do not know how to read file names to array
name=somefile
if [[ -e $name.jpg]] ; then
i=0
while [[ -e $name-$i.jpg]] ; do
let i++
done
name=$name-$i
fi
touch $name.jpg
You can have:
new_file=${file%%[^0-9]*.jpg}.jpg
As a concept you can have this to rename files:
for file in *.jpg; do
[[ $file == [0-9]*[^0-9]*.jpg ]] || continue ## Just a simple check.
new_file=${file%%[^0-9]*.jpg}.jpg
[[ -e $new_file ]] || continue ## Do not overwrite. Delete line if not wanted.
echo "Renaming $file to $new_file." ## Optional message.
mv -- "$file" "$new_file" || echo "Failed to rename $file to $new_file."
done
If you're going to process files that also contain directory names, you'll need some more changes:
for file in /path/to/other/dirs/*.jpg *.jpg; do
base=${file##*/}
[[ $base == [0-9]*[^0-9]*.jpg ]] || continue
if [[ $file == */* ]]; then
new_file=${file%/*}/${base%%[^0-9]*.jpg}.jpg
else
new_file=${file%%[^0-9]*.jpg}.jpg
fi
[[ -e $new_file ]] || continue
echo "Renaming $file to $new_file."
mv -- "$file" "$new_file"
done
you can also try the following code
but be careful all the files should be in .jpg format and pass the name of folder as an argument
#!/bin/bash
a=`ls $1`
for b in $a
do
echo $b
if (( i<4 ))
then
c=`echo $b | cut -c1-5`
let i=i+1
c="$c$i.jpg"
echo $c
else
c=`echo $b | cut -c1-5`
c="$c.jpg"
break
fi
mv $1$b $1$c
done

bash returning from recursion

I have to search all subdirs recursively and print *(number of * = depth of file/dir) type and name. The problem comes when i enter dir and then want to get out but nothing happens.
my test file
DIR test
*FILE ace
*FILE base
*DIR father
**FILE cookies
*DIR mother
**DIR how
***FILE youdoing
*FILE zebra
my code
maxDepth is how far in to the dir it can go(default 3) and currDepth is 1 at the beginning
function tree(){
maxDepth=$2
currDepth=$3
#print the starting file
if [ "$currDepth" -eq 0 ];then
printf "%s %s\n" DIR "$1"
currDepth=1
fi
for path in "$1"/*;do
for i in $( seq 1 $currDepth );do
echo -n *
done
if [ -d "$path" ];then
printf "%s %s\n" DIR "${path##*/}"
if [[ "$currDepth" -lt "$maxDepth" ]];then
tree "$path" "$maxDepth" "$(( currDepth + 1 ))"
fi
continue
fi
if [ -f "$path" ];then
printf "%s %s\n" FILE "${path##*/}"
continue
fi
if [ -L "$path" ];then
printf "%s %s\n" LINK "${path##*/}"
continue
fi
done
}
my output
DIR test
*FILE ace
*FILE base
*DIR father
**FILE cookies
**DIR mother
***DIR how
***FILE zebra
what am i doing wrong
Debug your script by doing set -x before you run it.
Make sure integers are always integers by declaring them with the -i integer attribute.
Use expression syntax consistently. It's a good idea to always use [[ ]] tests for string comparisons and (( )) for arithmetic and numeric comparisons, if your target shell is bash.
Use (( )) for loops instead of seq, which is nonstandard.
Explicitly declare your variables in the function (using local or declare) to ensure they are scoped to the function.
Actually call the inner tree.
#!/bin/bash
tree() {
local -i maxDepth=$2 # Make sure these values are always integers
local -i currDepth=$3
# print the starting file
if (( currDepth == 0 )); then # use bash arithmetic
printf "%s %s\n" DIR "$1"
currDepth=1
fi
for path in "$1"/*;do
for ((i=0; i<currDepth; i++)); do
printf '*'
done
if [[ -d "$path" ]];then
printf "%s %s\n" DIR "${path##*/}"
if [[ "$currDepth" -lt "$maxDepth" ]];then
tree "$path" "$maxDepth" "$(( currDepth + 1 ))"
fi
continue
fi
if [[ -f "$path" ]];then
printf "%s %s\n" FILE "${path##*/}"
continue
fi
if [[ -L "$path" ]];then
printf "%s %s\n" LINK "${path##*/}"
continue
fi
done
}
Here a solution using find, stat and sed:
find <DIR> -exec stat --printf="%n,%F\n" "{}" \; | \
sed -r -e "s/[^\/]+\//\*/g" -e "s/regular file/FILE/" -e "s/directory/DIR/" | \
sed -r -e "s/([\*]+)([^,]+),(.+)/\1 \3 \2/"
IMPORTANT: Use DIR not DIR/ otherwise DIR name will not appear in results.
Explanation:
find returns recursively all files and directory within DIR.
-exec option in find allows to pass each result to another command.
Here I'm passing each result to the command stat
stat has an option to format the output -printf (see manpage) :
%n is the filename (with relavtive path)
%F is the file type ( regular file, directory,symbolic link,block special file...)
So,
find <DIR> -exec stat --printf="%n,%F\n" "{}" \;
returns the following, one result by line (assuming that there are only regular files and directories in DIR) :
DIR/path/to/file,regular file
DIR/path/to/dir,directory
Then, I'm using sed to transform each line the way you required using regular expression:
Replace string/ by * -> ***basename,file type
Replace "regular file" by FILE
Replace "directory" by DIR
Shuffle around basename and filetype using back referencing in sed.
NOTE: I will not explain in details how regular expressions work as it would be too long.
I should have used local in front of currDepth=$3

Check if each file in a list (which is in a file) exists in bash

I have a text file (ListOfAllFiles.txt) that has a list of 500 files some of which exist and some don't.
I'd like to make two texts files that indicate which files exist and which don't.
This is my code thus far:
#!/bin/bash
for f in $(cat /path/to/ListOfAllFiles.txt)
do
if [[ -f $f ]]; then
echo $f > /path/to/FilesFound.txt
else
echo $f > /path/to/FilesNOTFound.txt
fi
done
What am I doing wrong??
Your biggest problem is that each pass through the loop will overwrite either /path/to/FilesFound.txt or /path/to/FilesNOTFound.txt; instead of using >, you should be using >>. Fixing that, and making other improvements for robustness, we get:
#!/bin/bash
echo -n > /path/to/FilesFound.txt # reset to empty file
echo -n > /path/to/FilesNOTFound.txt # reset to empty file
while IFS= read -r f ; do
if [[ -f "$f" ]]; then
echo "$f" >> /path/to/FilesFound.txt
else
echo "$f" >> /path/to/FilesNOTFound.txt
fi
done < /path/to/ListOfAllFiles.txt

BASH: comparing strings, one from a file, one in my program, if statement always is true

So I have this block of code. Basically, I'm taking file $i, checking if it's got content or not, checking if I can read it, if I can open it, grab the first line and see if it's a bash file. When I run this every time on a non-empty file, it was registers as true and echo's bash.
## File is empty or not
if [[ -s $i ]]
then
## Can we read the file
if [[ -r $i ]]
then
## File has content
if [[ $(head -n 1 $i) = "#! /bin/bash" ]]
then
echo -n " bash"
fi
fi
else
## file does not have content
echo -n " empty"
fi
This is what does the check of if it's bash:
if [[ $(head -n 1 $i) = "#! /bin/bash" ]]
Replace [[ with [ and enclose $(head -n 1 $i) in quotes.
[[ is itself an operator that tests its contents.

Resources