Error when executing script

Hi,

I wrote this script to test if the output for DIR1 and DIR2 comes out as I want :

#!/bin/bash

DAY=$(date +%d)
MONTH=$(date +%b)
YEAR=$(date +%Y)
DIR1=$($MONTH$YEAR"_Blast_BC01")
DIR2=$($MONTH$YEAR"_Blast_BC15")

echo $DIR1
echo $DIR2

This is the output I want for echo $DIR1 : Apr2017_Blast_BC01
And this is the output I want for echo $DIR2 : Apr2017_Blast_BC15

This is the error I get when I execute the script :

[root@L28tstream1 ~]# ./script3.sh
./script3.sh: line 6: Apr2017_Blast_BC01: command not found
./script3.sh: line 7: Apr2017_Blast_BC15: command not found

What is wrong with the syntax in the script above?

Hi,

the construct $(command) is called command substitution and evaluates to the output of command and you use it correctly for the assignment of the variables DAY, MONTH and YEAR. You do not want to use command substitution when you assign the values for DIR1 and DIR2.

#!/bin/bash

DAY=$(date +%d)
MONTH=$(date +%b)
YEAR=$(date +%Y)
DIR1=$MONTH$YEAR"_Blast_BC01"
DIR2=$MONTH$YEAR"_Blast_BC15"

echo $DIR1
echo $DIR2

Thanks! That did the trick!

It would be safer to wrap the entire assignment in double quotes as a common practice so that if you accidentally assign a space to either variable, then the assignment to DIR1 or DIR2 will not fail:-

HI_VAR="TextOnly"
LO_VAR="Text and space"

MY_VAR1="${HI_VAR}${LO_VAR}_other_text"
MY_VAR2=${HI_VAR}${LO_VAR}"_other_text"       # May fail in some shells

I always clearly mark the variable name with curly braces { & } to ensure I don't confuse the variable with any literal text or underscores that follow. You can get trapped with things like this:-

/home/rbatte1> V1="Hello"
/home/rbatte1> V2="$V1_Goodbye"
/home/rbatte1> echo "\$V2=\"$V2\""
$V2=""

This is because the assignment is looking for variable V1_Goodbye (equivalent to ${V1_Goodbye} ) which has not been set.

I would also suggest that you could consolidate commands such that you just do this:-

MONTHYEAR=$(date +%b%Y)
DIR1="${MONTHYEAR}_Blast_BC01"

Of course, it depends what you use the values for elsewhere.

I hope that this helps,
Robin

Yes, a consolidation makes sense.

MONTHYEAR=$(date +%b%Y)
DIR1=${MONTHYEAR}_Blast_BC01
DIR2=${MONTHYEAR}_Blast_BC15

"" Quoting of variables is never a mistake. Especially beginners should do it always!
But - now for the advanced user - the quoting of an assignment value is only needed if there is a literal space.

DIR3="${MONTHYEAR} Blast BC15"
echo "$DIR3"

Not if the space comes from a variable

NAME="Blast BC15 *"
DIR3=${MONTHYEAR}_$NAME
echo "$DIR3"

The latter is not an assignment - the argument for the echo command must be quoted!
Other

case ${MONTHYEAR}_$NAME in ...
[[ ${MONTHYEAR}_$NAME ... ]]

In all other cases the shell does first variable substitution then word splitting then globbing.
Last but not least, the [ ] is a false friend:

[ "${MONTHYEAR}_$NAME" ... ]

In fact the [ is a command and its arguments must be quoted!

I have another question on command substitution.
The same script, but different command substitution.

I simply run this command from the terminal :

[root@L28tstream1 emokheng]# stat -c %z * | awk '{print $1}'
2015-08-18
2015-08-18
[root@L28tstream1 emokheng]# 

As you see, I get the dates of the file generated in the directory.

However, when I attempt to use this command as a substitute, like this, I get a diferent output :

[root@L28tstream1 emokheng]# D=$(stat -c %z * | awk '{print $1}')
[root@L28tstream1 emokheng]# echo $D
2015-08-18 2015-08-18

It all displays in one line. Why is this, and how do I get it to display in two separate lines like the command above this?

Actually the main output I want is to get ONLY the day section of the whole date. Means I want to extract only '18' from 2015-08-18. This is for all the files in the directory.
Therefore, I use this command :

[root@L28tstream1 emokheng]# FDAY=$(date -d "$D" '+%d')
date: invalid date `2015-08-18\n2015-08-18'
[root@L28tstream1 emokheng]# 

However, as you can see, the error is shown. Perhaps if

echo $D

displays two separate lines, the error above will not happen?

How do I display ONLY the day part of the whole date for a file in a directory?

The -d option of date takes one single date only, not several separated by a line feed char. How about

D="$(stat -c %z sh* | awk -F"[ -]" '{print $3}')"
echo "$D"
03
03
1 Like

I don't know if the method I am using is complex, but the aim of this script is to generate reports every 1st, 7th and 15th of every month. However, the reports won't be generated until a few days after these dates. Like the reports for the 1st will be generated on the 5th of the month for example. And reports for the 7th will be generated on the 9th or 10th. And reports for the 15th will be generated on the 17th. These days of course will have to fall on a weekday, and not a weekend, which is why we cannot put a fixed date, for each batch.

For the reports on the 1st, I have to copy files dated 2nd onwards from the main directory that it was generated in, into another directory.
This is why I am cracking my head on how to get this done.

For example, these are the files listed in the main directory :

-rw-r-----  1 Debian-exim adm  1705192 May  1 06:25 mainlog.5.gz
-rw-r-----  1 Debian-exim adm 16737648 May  2 06:25 mainlog.4.gz
-rw-r-----  1 Debian-exim adm 11031310 May  3 06:33 mainlog.3.gz
-rw-r-----  1 Debian-exim adm  8258806 May  4 06:28 mainlog.2.gz
-rw-r-----  1 Debian-exim adm 37434708 May  5 06:26 mainlog.1
-rw-r-----  1 Debian-exim adm 14248899 May  5 17:43 mainlog
root@L28mustang:/var/log/exim4#

I have to copy these files, as you can see, dated May 2nd onwards till the latest (in this case, the 5th) to another directory. How do I create a bash script to do this? These are however not the only files here. There are older files from previous months also here. I need to copy over only the latest months' files dated 2nd onwards to another directory. The script has to be intelligent enough to recognize the files needed for that particular batch (1st, 7th, or 15th), and copy it over to another directory.

Then once it has been copied over to the other directory, this command has to be executed :

exigrep L28stream1 mainlog.2.gz | egrep -v "jiun.shyong.hor@ericsson.com|chander.c.shekher@ericsson.com|nagios|L28eagle" > mainlog.2.filtered

Means the oldest mainlog file will be labelled with the higher number. In the example above, the date of the oldest file was 16th (this was for batch 15). The report was generated on the 17th. Therefore the name of the oldest mainlog file will be "mainlog.2". Then there were 2 mainlog files generated on the 17th, which was mainlog.1 and mainlog.

This is the script that I have created so far. Any corrections and additional advise will definitely be very appreciated :

#!/bin/bash

DAY=$(date + %d)
MONTH=$(date +%b)
YEAR=$(date +%Y)
BC01="Blast_BC01"
BC15="Blast_BC15"
DIR1=$MONTH$YEAR_$BC01
DIR2=$MONTH$YEAR_$BC15
FOLDER=$YEAR

DIGMON=$(date +%m)

D=$(stat -c %z mainlog.* | awk -F"[ -]" '{print $3}')
FDAY=$(date -d "$D" '+%d')
FMON=$(date -d "$D" '+%m')
FYEAR=$(date -d "$D" '+%Y')

FILEDATE=$YEAR"-"$DIGMON"-"$i

cd /var/log/exim4

if [ ! -d "$YEAR" ]; then
    mkdir $YEAR
        if [ $DAY < 15 ];then
            mkdir -p /var/log/exim4/$YEAR/$DIR1
            for (( $D=2; $D<=$DAY; $D++ ))
                cp -p mainlog* ./$YEAR/$DIR1
            
        else
            mkdir -p /var/log/exim4/$YEAR/$DIR2
            for (( $D=16; $D<=$DAY; $D++ ))
                cp -p mainlog* ./$YEAR/$DIR2
        fi
else
    cd $FOLDER
        if [ $DAY < 15 ];then
            mkdir -p /var/log/exim4/$YEAR/$DIR1
            for ($D=2; $D<$DAY; $D++)
                cp -p mainlog* ./$YEAR/$DIR1
                    cd /$YEAR/$DIR1
                    count=$(ls -lrt | wc -l)
                    for (i=;i<=$count;i++)
                    exigrep L28stream1 mainlog.$count.gz | egrep -v "jiun.shyong.hor@ericsson.com|chander.c.shekher@ericsson.com|nagios|L28eagle" > mainlog.$count.filtered
            
        else
            mkdir -p /var/log/exim4/$YEAR/$DIR2
            for ($D=16; $D<$DAY; $D++)
                cp -p mainlog* ./$YEAR/$DIR2
        fi
fi

---------- Post updated at 06:32 PM ---------- Previous update was at 06:18 PM ----------

For TESTING purposes ONLY, however, I tried running some commands from the terminal to copy files from the directory /home/emokheng to /home/emoaigin :

These are the files in /home/emokheng :

[root@L28tstream1 emokheng]# ls -lrt
total 8
-rw-rw-r-- 1 emokheng emokheng 328 Aug 18  2015 foreign.pl
-rw-rw-r-- 1 emokheng emokheng  91 Aug 18  2015 afterinstallforeign.pl

As you can see the dates of the files are August 18th. For report generation this will be considered batch 15. These are the steps I have performed :

[root@L28tstream1 emokheng]# D="$(stat -c %z * | awk -F"[ -]" '{print $3}')"
[root@L28tstream1 emokheng]# echo "$D"
18
18
[root@L28tstream1 emokheng]# for (( "$D"=16; "$D"<=20; "$D"++ ));
> do
> cp -r * /home/emoaigin/
> done
-bash: ((: 18
18=16: syntax error in expression (error token is "18=16")
[root@L28tstream1 emokheng]#

What is wrong in these steps? Why does it not recognize files dated 18th and copy it over?

You can't use it like that: for (( "$D"=16; "$D"<=20; "$D"++ )); . Use unexpanded variable names like for (( i=1; i<5; i++ )); do echo $i; done . $D is already expanded by the shell.

But I want to copy files with a certain date over from one dir to another.
How do i do this?

I tried using the for loop like this :

[root@L28tstream1 emokheng]# for (( i=16; i<20; i++ ));
> do
> cp -r * /home/emoaigin/;
> done
cp: overwrite `/home/emoaigin/afterinstallforeign.pl'? y
cp: overwrite `/home/emoaigin/foreign.pl'? y
cp: overwrite `/home/emoaigin/afterinstallforeign.pl'? y
cp: overwrite `/home/emoaigin/foreign.pl'? y
cp: overwrite `/home/emoaigin/afterinstallforeign.pl'? y
cp: overwrite `/home/emoaigin/foreign.pl'? y
cp: overwrite `/home/emoaigin/afterinstallforeign.pl'? y
cp: overwrite `/home/emoaigin/foreign.pl'? y
[root@L28tstream1 emokheng]# 

As you can see, it does the loop for times, (i.e copying 2 files 4 times).
This is not I want. What I want is for the script to recognize the files dated from the 16th to the latest date, and copy it over to the other directory, without repeating or overwriting like above.

Is this possible?

Again, the $D is an argument and need to be in quotes.

echo $D

lets the shell do variable substitution then word splitting then globbing (replace wildcards with matching filenames). The word splitting reformats the line breaks to spaces.

echo "$D"

lets the shell do variable substitution only.

Thank you so much for all the comments and suggestions.

I have now managed to compile the whole script, but still needs a bit more tweaking as there are few more errors.

Part of my script does this :

new=1
for f in `find . -name '*.filtered' | sort -r`; do                                                                  
> eximstats -nr "$f" > "eximstats_day"$new"_"$eximBC01.txt";
> eximstats -xls -nr  "$f" > "eximstats_day"$new"_"BC01.xls";
> ((new++));
> done

This returns errors like this :

bash: eximstats_day1_01082017.txt;
eximstats -xls -nr  ./mainlog.7.filtered > eximstats_day1_BC01.xls: No such file or directory
bash: eximstats_day2_01082017.txt;
eximstats -xls -nr  ./mainlog.6.filtered > eximstats_day2_BC01.xls: No such file or directory
bash: eximstats_day3_01082017.txt;
eximstats -xls -nr  ./mainlog.5.filtered > eximstats_day3_BC01.xls: No such file or directory
bash: eximstats_day4_01082017.txt;
eximstats -xls -nr  ./mainlog.4.filtered > eximstats_day4_BC01.xls: No such file or directory
bash: eximstats_day5_01082017.txt;
eximstats -xls -nr  ./mainlog.3.filtered > eximstats_day5_BC01.xls: No such file or directory
bash: eximstats_day6_01082017.txt;
eximstats -xls -nr  ./mainlog.2.filtered > eximstats_day6_BC01.xls: No such file or directory

However, when I execute the code directly on the terminal, there is no error (without the for loop) :

# eximstats -nr  mainlog.7.filtered > eximstats_day1_01082017.txt
# eximstats -xls -nr  mainlog.7.filtered > eximstats_day1_01082017.xls

Is there something wrong with the syntax?

---------- Post updated 08-09-17 at 06:24 PM ---------- Previous update was 08-08-17 at 07:19 PM ----------

I found out that the error was variable substitution. I corrected the code like this, and it worked properly :

new=1
for f in `find . -name '*.filtered' | sort -r`; do
       eximstats -nr "$f" > eximstats_day"$new"_"$eximBC07".txt;
       eximstats -xls -nr  "$f" > eximstats_day"$new"_"$eximBC07".xls;
       ((new++));
done

However, I have another part of the script that has this line :

eximstats -merge eximstats_day*.txt > "eximstats.consolidated_$MONTH$YEAR.txt"

I did not put the "" for the $MONTH$YEAR, but it still produced the output I wanted. Why is this?

So far you did fairly well. A few comments to your code:

new=1
for f in `find . -name '*.filtered' | sort -r`; do
       eximstats -nr "$f" > eximstats_day"$new"_"$eximBC07".txt;
       eximstats -xls -nr  "$f" > eximstats_day"$new"_"$eximBC07".xls;
       ((new++));
done

First, you should not use backticks for command substitution any more. If you are not using an original Bourne shell (which would only know the backticks and nothing else) you should use the POSIX-construct you had at the beginning of the thread:

for f in $(find . -name '*.filtered' | sort -r) ; do

Second, you need only one quote for a whole string. i.e. replace this:

eximstats -nr "$f" > eximstats_day"$new"_"$eximBC07".txt

with this:

eximstats -nr "$f" > "eximstats_day${new}_${eximBC07}.txt"

Third, you need no ";" at the end of a line. You only need them if you have more than one command on the same line:

command1 ; command2

but:

command1
command2

and a last point: when you use integer environments you should have spaces surrounding them. It is not only easier to read but some shells are quite picky about this:

((i=i+1))            # might work, but this
(( i=i+1 ))          # is safer and this:
(( i = i + 1 ))      # might even be easier to read

Finally, a suggestion: when you write scripts you should try to avoid relative pathes. If you use "." or ".." in a script it tends to work if called in one directory but fail if called in another. Better to avoid this sort of risk and only use absolute pathes.

So, here is like i would write it:

new=1
for f in $(find /some/directory -name '*.filtered' | sort -r) ; do
       outfile="/some/directory/eximstats_day${new}_${eximBC07}"
       eximstats       -nr "$f" > "${outfile}.txt"
       eximstats -xls -nr "$f" > "${outfile}.xls"
       (( new++ ))
done

A string is a string. Hence, this:

string="abc"def

and this:

string="abcdef"

produces the same. It would not produce the same only if in the part not covered by quotes would contain something the shell interprets: blanks, tabs, newlines, and so on. Therefore you can (and in fact should, as i said above) cover always your whole string into a single pair of double quotes:

var="with spaces in it"
echo "this is a string ${var} - and here it continues"

I hope this helps.

bakunin