shell script: "'<<' unmatched"-syntax error using here document - shell

Hi I am attempting to write a program that will alert the user if a person of interest has come online at a given time. My program thus far is
#!/usr/bin/ksh
message=""
when=""
validFiles=""
validUsers=""
if [ $# -gt 0 ] ; then
while getopts w:m: opt
do
case $opt in
w) when=$OPTARG;;
m) message=$OPTARG;;
\?) echo $USAGE exit 2;;
esac
done
shift $(($OPTIND - 1))
if [[ $# -gt 0 ]] ; then
for i; do
if [[ -f "$i" && -r "$i" ]]; then
if ! echo $validFiles | grep $i >/dev/null; then
validFiles="$validFiles $i"
fi
elif id $i 2> /dev/null 1>&2; then
if ! echo $validUsers | grep $i > /dev/null; then
validUsers="$validUsers $i"
fi
fi
done
if [[ $when != "" && $validFiles != "" || $validUsers != "" ]] ;then
for i in $validUsers; do
if ! grep $i $validFiles >/dev/null; then
at $when <<"END"
if finger $i | grep $i; then
echo "$i is online" | elm $message
fi
END
fi
done
fi
else
echo "No files or usernames"
fi
else
echo "No arguments provided"
fi
My problem is that when I attempt to run this I get the error message
syntax error at line 33 : `<<' unmatched
I am not sure as to why this is appearing. I have checked many other examples and my at command,here document, appears to be the same as theirs. Could anybody help me out? Thanks.

The here string delimiter must not be indented, your END should be at the beginning of the line:
$ cat <<EOT
> foo
> bar
> EOT
foo
bar
If you want the trailing delimiter to be indented you can use the following syntax, but this will also strip all leading tabs from the here document itself (this only works with tabs!):
$ cat <<-EOT
> foo
> bar
> quux
> EOT
foo
bar
quux
Note that this behaviour is specified by POSIX so should work in all compliant shells:
If the redirection symbol is "<<-", all leading tabs shall be stripped from input lines and the line containing the trailing delimiter.

Related

How to add literal line - that contain $ signs - to a file, if line does not appear in file already?

Given the following literal line (namely, $'s are not introducing variables):
if $var-with-dollar-and-space == 'local2' and $msg contains 'Disp' then /path/to/file
I have to check if that line appears in a file (exactly as is), and if not - to add it to the file.
I tried many variations of escaping characters - but cannot get it right.
Following is an attempt at an MCVE (not working):
#!/bin/bash -xv
LINE_TO_ADD_IF_ABSENT="if \$var-with-dollar-and-space == 'local2' and \$msg contains 'Disp' then /path/to/file"
ESCAPED_LINE='if $var-with-dollar-and-space == \'local2\' and $msg contains \'Disp\' then /path/to/file'
LOG_FILE='/tmp/mcve.log'
if [[ ! -e $LOG_FILE ]]; then
touch $LOG_FILE
fi
if [[ -w $LOG_FILE ]]; then
$( fgrep -q "$ESCAPED_LINE" ${LOG_FILE} )
ret=$?
if [[ $ret != 0 ]]; then
echo $ESCAPED_LINE >> $LOG_FILE
fi
fi
Can you suggest an amendment (or a different approach), that will enable me to create a bach script that will add the line at the top to the file, if the line - as is - is not included in the file?
Edit:
In response to #Charles' and #David's comments, following is an amended MCVE and the results of its running:
$ cat /tmp/mcve.sh
#!/bin/bash -xv
line_to_add_if_absent='if $var-with-dollar-and-space == \'local2\' and $msg contains \'Disp\' then /path/to/file'
escaped_line='if $var-with-dollar-and-space == \'local2\' and $msg contains \'Disp\' then /path/to/file'
log_file="/tmp/mcve.log"
if [[ ! -e $log_file ]]; then
touch $log_file
fi
if [[ -w $log_file ]]; then
fgrep -q ${line_to_add_if_absent} ${log_file}
ret=$?
if [[ $ret != 0 ]]; then
echo $escaped_line >> $log_file
fi
fi
$ /tmp/mcve.sh
#!/bin/bash -xv
line_to_add_if_absent='if $var-with-dollar-and-space == \'local2\' and $msg contains \'Disp\' then /path/to/file'
escaped_line='if $var-with-dollar-and-space == \'local2\' and $msg contains \'Disp\' then /path/to/file'
log_file="/tmp/mcve.log"
if [[ ! -e $log_file ]]; then
touch $log_file
fi
if [[ -w $log_file ]]; then
fgrep -q ${line_to_add_if_absent} ${log_file}
ret=$?
if [[ $ret != 0 ]]; then
echo $escaped_line >> $log_file
fi
fi
/tmp/mcve.sh: line 4: unexpected EOF while looking for matching `''
/tmp/mcve.sh: line 20: syntax error: unexpected end of file
$
The following works fine in practice:
#!/bin/bash -xv
line_to_add_if_absent="if \$var-with-dollar-and-space == 'local2' and \$msg contains 'Disp' then /path/to/file"
log_file='/tmp/mcve.log'
[[ -e $log_file ]] || touch -- "$log_file"
if [[ -w $log_file ]]; then
if fgrep -q -e "$line_to_add_if_absent" "$log_file"; then
: "do nothing here; line is already present"
else
printf '%s\n' "$line_to_add_if_absent" >>"$log_file"
fi
fi
The only change I had to make was removing the unnecessary/useless ESCAPED_LINE, and using the variable with literal contents. (Quoting has also been fixed; echo $ESCAPED_LINE is unreliable, and echo was replaced with printf, as backslashes in echo statements have undefined behavior; see also the APPLICATION USAGE and RATIONALE sections of the POSIX specification for echo)

bash- reading file from stdin and arguments

So I have googled this and thought I found the answers, but it still doesnt work for me.
The program computes the average and median of rows and columns in a file of numbers...
Using the file name works:
./stats -columns test_file
Using cat does not work
cat test_file | ./stats -columns
I am not sure why it doesnt work
#file name was given
if [[ $# -eq 2 ]]
then
fileName=$2
#file name was not given
elif [[ $# -eq 1 ]]
then
#file name comes from the user
fileName=/dev/stdin
#incorrect number of arguments
else
echo "Usage: stats {-rows|-cols} [file]" 1>&2
exit 1
fi
A very simple program that accepts piped input:
#!/bin/sh
stdin(){
while IFS= read -r i
do printf "%s" "$i"
done
}
stdin
Test is as follows:
echo "This is piped output" | stdin
To put that into a script / utility similar to the one in the question you might do this:
#!/bin/sh
stdin(){
while IFS= read -r i
do printf "%s" "$i"
done
}
rowbool=0
colbool=0
for i in $#
do case "$i" in
-rows) echo "rows set"
rowbool=1
shift
;;
-cols) echo "cols set"
colbool=1
shift
;;
esac
done
if [[ $# -gt 0 ]]
then
fileName=$1
fi
if [[ $# -eq 0 ]]
then fileName=$(stdin)
fi
echo "$fileName"

Getting command line argument that stores in a variable

I am writing a bash script to finger the first three line of user's info.
ex:
$ ./c.sh bob unknown
Login: bob Name: Bob
Directory: /u1/h7/bob Shell: /bin/tcsh
Office: AA 044, x8361 Home Phone: 000-000-0000
unknown: no such user.
Here is my code so far
#!/bin/bash
if [ $# == 0 ]; then
echo "Usage: ./c.sh Login/Username"
exit
else
i=$#
j=1
while [ "$j" -le "$i" ]; do
finger ${$j} | head -n+3
echo
j=$(($j+1))
done
fi
instead of giving what user types for the command line arguments, ${$j} is giving me the the value of $j, any suggestion and help for how to get the login/username? I've tried $($j), $((j)), ${$j}....
The easy answer: stop using unnecessary indirection:
#!/bin/bash
if (( $# == 0 )); then
echo "Usage: ./c.sh Login/Username"
exit
else
while [[ $1 ]]; do
finger "$1" | head -n+3
echo
shift
done
fi
or…
…
for user; do # equivalent to `for user in "$#"; do`
finger "$user" | head -n+3
…
done
You could write it this way:
i=$#
j=1
while [ $j -le $i ]; do
finger "${#:j++:1}" | head -n+3
echo
done
…but you don't need to work that hard.
#!/bin/bash
if [[ $# -eq 0 ]]; then
echo "Usage: $0 Login/Username"
exit
else
for ARG in "$#"; do
finger "$ARG" | head -n 3
echo # If you want a newline
done
fi
As simple as it can be.

unary operator expected with more than 1 argument

for var in "$#"
do
if test -z $var
then
echo "missing operand"
elif [ -d $var ]
then
echo "This is a directory"
elif [ ! -f $var ]
then
echo "The file does not exist"
else
basename=$(basename $var)
dirname=$(readlink -f $var)
inodeno=$(ls -i $var| cut -d" " -f1)
read -p "remove regular file $#" input
if [ $input = "n" ]
then exit 1
fi
mv $var "$var"_"$inodeno"
echo "$basename"_"$inodeno":"$dirname" >> $HOME/.restore.info
mv "$var"_"$inodeno" $HOME/deleted
fi
done
**Hello, the above code is trying to mimic the rm command in unix. Its purpose is to remove the file .
Eg if I type in bash safe_rm file1 , it works however if type in
bash safe_rm file1 file 2 , it prompts me to remove file 1 twice and gives me a unary operater expected for line 27(if [ $input = "n" ]).
Why does it not work for two files, ideally I would like it to prompt me to remove file1 and file 2.
Thanks
read -p "remove regular file $#" input
should probably be
read -p "remove regular file $var" input
That's the basic.
And this is how I'd prefer to do it:
for T in "$#"; do
if [[ -z $T ]]; then
echo "Target is null."
elif [[ ! -e $T ]]; then
echo "Target does not exist: $T"
elif [[ -d $T ]]; then
echo "Target can't be a directory: $T"
else
BASE=${T##*/}
DIRNAME=$(exec dirname "$T") ## Could be simpler but not sure how you want to use it.
INODE_NUM=$(exec stat -c '%i' "$T")
read -p "Remove regular file $T? "
if [[ $REPLY == [yY] ]]; then
# Just copied. Not sure about its logic.
mv "$T" "${T}_${INODE_NUM}"
echo "${BASE}_${INODE_NUM}:${DIRNAME}" >> "$HOME/.restore.info"
mv "${T}_${INODE_NUM}" "$HOME/deleted"
fi
fi
done

Logical Error in Shell script.Please help (UNIX)

Following is a source code which takes in only 'files',lists the file permissions of a file and prints the output by replacing
r=READ,w-WRITE,x-EXECUTABLE.
It should also echo "User".But the My problem here is that I have replaced '-' by User but then if the file has a permission of r--x,it also prints "User" # that point.I know its not a correct way to go about it.Can anyone suggest me a better way of echoing "User".
I have also tried printing it before the loop but then it won't serve my purpose, as My program only works withe file permissions of a FILE and not any block/socket/pipe/directory/etc.
#!/bin/bash
if [ $# -lt 1 ];then
echo "USAGE: $0 file-name"
exit 1
fi
ls -l $1 | cut -c1-4 | tr "\012" "." > fp
i=1
while(($i <= 4))
do
p=`cat fp | cut -c$i`
case $p in
[dbsplc] | t) echo "not a file";
exit 1;;
-) echo "User";;
r) echo "READ";;
w) echo "WRITE";;
x) echo "EXECUTE";;
esac
((++i))
done
exit 0
Too complicated. You don't have to rely on ls at all:
#!/bin/bash
if [[ $# -lt 1 ]]; then
echo "USAGE: $(basename "$0") filename ..."
exit 1
fi
exit_status=0
for file in "$#"; do
if [[ ! -f "$file" ]]; then
echo "not a file: $file" >&2
exit_status=$(( exit_status + 1 ))
continue
fi
echo "$file:"
echo "User"
[[ -r "$file" ]] && echo "READ"
[[ -w "$file" ]] && echo "WRITE"
[[ -x "$file" ]] && echo "EXECUTE"
done
exit $exit_status
I'd just use stat -c %a and process that instead.
an exemple using awk (easily adaptable to your program)
ll |awk '{
rights=substr($1, 2, 3);
sub(/r/, "READ ", rights);
sub(/w/, "WRITE ", rights);
sub(/x/, "EXECUTE ", rights);
print rights $3
}'
Explanations :
rights=substr($1, 2, 3);
$1 contains rights of your program and we only takes the 3 first rights (user one)
sub(/r/, "READ ", rights);
Substiture "r" with READ in rights (and so on).
print rights $3
Print rights (substituated) and $3 that contains the user name.
This served my purpose,I separated the first condition into a different case-statement.:
#!/bin/bash
if [ $# -lt 1 ];then
echo "USAGE: $0 file-name"
exit 1
fi
ls -l $1 | cut -c1-4 | tr "\012" "." > fp
i=1
while(($i == 1))
do
p=`cat fp | cut -c$i`
case $p in
[dbsplc] | t) echo "not a file";
exit 1;;
esac
echo "User"
((++i))
done
while(($i <= 4))
do
p=`cat fp | cut -c$i`
case $p in
r) echo "READ";;
w) echo "WRITE";;
x) echo "EXECUTE";;
esac
((++i))
done
exit 0

Resources