Assume a bash script to which four commandline arguments are passed. Each of these arguments represents a path to, and a name of, an input file (i.e., INF1, INF2, INF3, INF4). I would like to evaluate for each of these four input files, if it exists and - if a file does not exist as specified - exit the script.
#!/bin/bash
# Assigning commandline arguments
INF1=$1
INF2=$2
INF3=$3
INF4=$4
# Check if input files exist
if [ ! -f $1 ]; then
echo -ne " ERROR | File not found: $1\n"
exit 1
fi
if [ ! -f $2 ]; then
echo -ne " ERROR | File not found: $2\n"
exit 1
fi
if [ ! -f $3 ]; then
echo -ne " ERROR | File not found: $3\n"
exit 1
fi
if [ ! -f $4 ]; then
echo -ne " ERROR | File not found: $4\n"
exit 1
fi
I would think that having four individual if-statements here is unnecessary, and that the same functionality can be achieved with a single if-statement, wrapped into a loop. How can I reduce the code in this aspect?
in a script
#!/bin/bash
for v; do
if [ ! -f "$v" ]; then
echo "ERROR | File not found: $v"
exit 1
fi
done
You can declare an array and assing the variables and loop through it.
ARRAY=()
ARRAY+=$1
ARRAY+=$2 ..
and
for i in "${arrayName[#]}"
do
:
# do whatever on $i
done
Related
I am trying to write a file that mimics the cat -n bash command. It is supposed to accept a filename as input and if no input is given print a usage statement.
This is what I have so far but I am not sure what I am doing wrong:
#!/bin/bash
echo "OK"
read filename
if [ $filename -eq 0 ]
then
echo "Usage: cat-n.sh file"
else
cat -n $filename
fi
I suggest to use -z to check for empty variable $filename:
if [ -z $filename ]
See: help test
Right. Basically what I want to achieve is i am trying to write a bash script which will take file as input argument and then load that file in the script and show all the lines in that file with the numbers bulletin. how can i do that.
i have tried nl -ba teat file but its not working
1 hii
2 whats up
3 how are you today
4 where have you been
5 whats going on
this is the sript i have written
#!/bin/bash
if [ $# == 0 ]; then
echo "ivalid argument"
elif [ $1 == $file ]; then
while read line
do
echo `nl $Line`
done
else
echo "wtf"
fi
See below modified script that does the job without using "nl":
#!/bin/bash
if [ -z "$1" ]; then
echo "ivalid argument"
exit 1;
fi;
cnt=1;
while read line; do
echo "$cnt $line";
((cnt++));
done<"$1";
I don't know, what
[ $1 == $file ]
is supposed to mean in your script, since you don't set a variable file anywhere, but here is one solution:
#!/bin/bash
nl -ba {$1?Tell me the name of the file or I will kick you}
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.
Script needed was
#!/bin/bash
# Check if there are two arguments
if [ $# -eq 2 ]; then
# Check if the input file actually exists.
if ! [[ -f "$1" ]]; then
echo "The input file $1 does not exist."
exit 1
fi
else
echo "Usage: $0 [inputfile] [outputfile]"
exit 1
fi
# Run the command on the input file
grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" "$1" > "$2"
Edit, the script has changed to
grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" $*
if [ ! -f "$1" ]; then
echo 'Usage: '
echo
echo './Scriptname inputfile > outputfile'
exit 0
fi
invoking the script with no parameters gives no erros and sits blank
Usage:
./Scriptname inputfile > outputfile
I have bit of code
grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" $*
This code pulls lines that have a single word on them and pumps the output to a new file, so for example
This is a multi word line
this
the above line is not
now
once again wrong
The output would be
This
now
The code works, users invoke the code using ./scriptname file > newfile
However, I am trying to expand the code to give users an error message if they invoke the script incorrectly.
For the error messange, I'm thinking of echoing something back like scriptname file_to_process > output_file.
I did try
if [incorrectly invoted unsure what to type]
echo $usage
exit 1
Usage="usage [inputfile] [>] [outputfile]
However I have had little luck. The code runs but does nothing if I invoke with just the script name. Also, if I invoke the script with just the scriptname and the input file, it will output the results instead of exiting with the error message.
Other ones I have tried are
if [ ! -n $1 ]; then
echo 'Usage: '
echo
echo './Scriptname inputfile > outputfile'
exit 0
fi
Given replies I have received so far, my code now is
#!/bin/bash
grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" $*
if [ ! -f "$1" ]; then
echo 'Usage: '
echo
echo './Scriptname inputfile > outputfile'
exit 0
fi
When invoking the script without an input file the script does nothing and has to be aborted with ctrl+c, still trying to get the echo of the invoke message.
When you are invoking the script like ./scriptname file > newfile, the shell interprets file as the only argument to ./scriptname. This is because > is the standard output redirection operator.
I would like to propose 2 possible alternatives:
Alternative 1:
Maybe you're can try passing it as 1 argument like this?
./scriptname 'file > newfile'
In that case one way to check the format would be
#!/bin/bash
# Check if the format is correct
if [[ $1 =~ (.+)' > '(.+) ]]; then
# Check if the input file actually exists.
if ! [[ -f "${BASH_REMATCH[1]}" ]]; then
echo "The input file ${BASH_REMATCH[1]} does not exist!"
exit 1
fi
else
echo "Usage: $0 \"[inputfile] [>] [outputfile]\""
exit 1
fi
# Redirect standard output to the output file
exec > "${BASH_REMATCH[2]}"
# Run the command on the input file
grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" "${BASH_REMATCH[1]}"
Note: If you are checking whether the arguments are valid or not, it's generally better to run commands only after the checking is done.
Alternative 2:
Passing 2 arguments like
./scriptname file newfile
The script looks like this
#!/bin/bash
# Check if there are two arguments
if [ $# -eq 2 ]; then
# Check if the input file actually exists.
if ! [[ -f "$1" ]]; then
echo "The input file $1 does not exist."
exit 1
fi
else
echo "Usage: $0 [inputfile] [outputfile]"
exit 1
fi
# Run the command on the input file
grep -P "^[\s]*[0-9A-Za-z-]+.?[\s]*$" "$1" > "$2"
I'd use parameter expansion for this:
inputfile=${1:?Usage: $(basename $0) inputfile > outputfile}
If the script is called without arguments (i.e. $1 is unset) the ${var:?error message} expansion causes the shell to display an error with the given message and exit. Otherwise the first argument is assigned to $inputfile.
Try to add double quotes around $1 and use -f to check for exists and is normal file:
if [ ! -f "$1" ]; then
echo 'Usage: '
echo
echo './Scriptname inputfile > outputfile'
exit 0
fi
Also you can check for the param count with $# and cat an usage message:
if [ ! $# -eq 1 ]; then
cat << EOF
Usage:
$0 'input_file' > output_file
EOF
exit 1
fi
How do i check for the correct number of arguments (one argument). If somebody tries to invoke the script without passing in the correct number of arguments, and checking to make sure the command line argument actually exists and is a directory.
#!/bin/sh
if [ "$#" -ne 1 ] || ! [ -d "$1" ]; then
echo "Usage: $0 DIRECTORY" >&2
exit 1
fi
Translation: If number of arguments is not (numerically) equal to 1 or the first argument is not a directory, output usage to stderr and exit with a failure status code.
More friendly error reporting:
#!/bin/sh
if [ "$#" -ne 1 ]; then
echo "Usage: $0 DIRECTORY" >&2
exit 1
fi
if ! [ -e "$1" ]; then
echo "$1 not found" >&2
exit 1
fi
if ! [ -d "$1" ]; then
echo "$1 not a directory" >&2
exit 1
fi
cat script.sh
var1=$1
var2=$2
if [ "$#" -eq 2 ]
then
if [ -d $var1 ]
then
echo directory ${var1} exist
else
echo Directory ${var1} Does not exists
fi
if [ -d $var2 ]
then
echo directory ${var2} exist
else
echo Directory ${var2} Does not exists
fi
else
echo "Arguments are not equals to 2"
exit 1
fi
execute it like below -
./script.sh directory1 directory2
Output will be like -
directory1 exit
directory2 Does not exists
You can check the total number of arguments which are passed in command line with "$#"
Say for Example my shell script name is hello.sh
sh hello.sh hello-world
# I am passing hello-world as argument in command line which will b considered as 1 argument
if [ $# -eq 1 ]
then
echo $1
else
echo "invalid argument please pass only one argument "
fi
Output will be hello-world