I am to write a basg=h script that displays a message if you input the correct number (i.e. 1 = function f1, 2 = function f2, and 3 = function f3)
My code is as follow
#!/bin/bash
function f1
{
echo "This message is from function 1"
}
function f2
{
echo "This messge is from function 2"
}
function f3
{
echo "This message is from function 3"
}
function=$(typeset -F)
declare -a myarr=(`echo "$function" [sed 's/declare[ ]-f / /g'`)
read -p "Enter a number (1, 2, or 3): " number
if ! [[ "$number" =~ ^[0-9]+$ ]]
then
echo "Not a valid number"
exit
fi
flag=0
for element in "${myarr[#]}
do
if echo "$element" | grep -q "$num"; then
$element
flag=1
fi
done
if [ "$flag" -eq 0 ]; then
echo "No function matches number $num"
fi
Now when I run the code I obtain the error
q6: line 43: unexpected EOF while looking for matching `"'
q6: line 45: syntax error: unexpected end of file
Any help sourcing the errors?
for element in "${myarr[#]}
Missing the end quote. You can catch errors like this by seeing where the syntax highlighting goes wonky. Notice how much of the script following this line is incorrectly colored red?
Even better, you can use ShellCheck:
Line 32:
for element in "${myarr[#]}
^-- SC1009: The mentioned syntax error was in this for loop.
^-- SC1078: Did you forget to close this double quoted string?
Fix that and you get:
Line 1:
#!/bin/bash
^-- SC1114: Remove leading spaces before the shebang.
Line 20:
declare -a myarr=(`echo "$function" [sed 's/declare[ ]-f / /g'`)
^-- SC2207: Prefer mapfile or read -a to split command output (or quote to avoid splitting).
^-- SC2006: Use $(..) instead of legacy `..`.
^-- SC2116: Useless echo? Instead of 'cmd $(echo foo)', just use 'cmd foo'.
Line 22:
read -p "Enter a number (1, 2, or 3): " number
^-- SC2162: read without -r will mangle backslashes.
Line 35:
if echo "$element" | grep -q "$num"; then
^-- SC2154: num is referenced but not assigned.
Related
I have this snippet below based on the idea to mask the input for a password by #SiegeX and #mklement0 from this question.
It's great and my only desired addition was to delete for length of entered chars only, so we're not wiping out the entire line.
I don't understand this very well, so have run into bugs.
With below, entering "12345" and backspacing, the numbers don't "backspace"; no error.
Entering "123FourFive" and backspacing, produces error: line 9: [[: 123FourFiv: value too great for base (error token is "123FourFiv")
Entering "OneTwo345" and backspacing, seems to work fine.
Entering symbols one might expect in a password and then backspacing produces error: line 9: [[: OneTwo./?: syntax error: invalid arithmetic operator (error token is "./?")
Also pressing arrow keys during input creates wild screen behaviour after backspacing...
How to improve this so we're masking user input, and only deleting what has been entered?
Or are we "reinventing the wheel"? Is there something else out there that will do what I'm trying to do already (which is mask user input to obtain a password in a bash script to put it in a variable)?
User environment is Linux Mint 19.3 with Cinnamon.
#!/bin/bash
printf "\n\tPlease enter password: "
# mask the input for the password by #SiegeX and #mklement0 (https://stackoverflow.com/questions/4316730)
while IFS= read -r -s -n1 char; do
[[ -z "${char}" ]] && { printf '\n'; break; } # ENTER pressed; output \n and break.
if [[ "${char}" == $'\x7f' ]]; then # backspace was pressed
# #nooblag, only delete for length of entered chars?
if [[ "${password}" -lt "${#password}" ]]; then printf '\b \b'; fi # erase one '*' to the left.
[[ -n $password ]] && password=${password%?} # remove last char from output variable
else
# add typed char to output variable
password+="${char}"
# print '*' in its stead
printf '*'
fi
done
printf "\tPassword: ${password}\n\n"
Update: askpass as suggested here nearly does what I'm after, but if the user tries to abort/kill it with Ctrl+C it messes up the terminal...
This might be the solution! Taken from here.
#!/bin/bash
#
# Read and echo a password, echoing responsive 'stars' for input characters
# Also handles: backspaces, deleted and ^U (kill-line) control-chars
#
unset PWORD
PWORD=
echo -n 'password: ' 1>&2
while true; do
IFS= read -r -N1 -s char
# Note a NULL will return a empty string
# Convert users key press to hexadecimal character code
code=$(printf '%02x' "'$char") # EOL (empty char) -> 00
case "$code" in
''|0a|0d) break ;; # Exit EOF, Linefeed or Return
08|7f) # backspace or delete
if [ -n "$PWORD" ]; then
PWORD="$( echo "$PWORD" | sed 's/.$//' )"
echo -n $'\b \b' 1>&2
fi
;;
15) # ^U or kill line
echo -n "$PWORD" | sed 's/./\cH \cH/g' >&2
PWORD=''
;;
[01]?) ;; # Ignore ALL other control characters
*) PWORD="$PWORD$char"
echo -n '*' 1>&2
;;
esac
done
echo
echo $PWORD
We've got a script running the last 2 commands. But it's failing..
$ if [ -d / ]; then echo a; else echo b; fi
a
$ bash -c 'if [ -d / ]; then echo a; else echo b; fi'
a
$ A="bash -c 'if [ -d / ]; then echo a; else echo b; fi'"
$ $A
[: -c: line 0: unexpected EOF while looking for matching `''
[: -c: line 1: syntax error: unexpected end of file
I really wonder why? Thanks!
In your last command line, you have assigned a value to the variable A, but failed to evaluate it before calling $A. Instead, any of the following will work:
Define the variable and then evaluate it,
~]# A="bash -c 'if [ -d / ]; then echo a; else echo b; fi'"
~]# eval $A
OR evaluate it first and then echo it.
~]# A=$(bash -c 'if [ -d / ]; then echo a; else echo b; fi')
~]# echo $A
OR not personally preferred, you can also use this:
~]# A="eval bash -c 'if [ -d / ]; then echo a; else echo b; fi'"
~]# $A
All of the above will result in a.
Note: See why you shouldn't use the third option: http://mywiki.wooledge.org/BashFAQ/048
Let's take a simpler example to explain:
$ A="bash -c 'echo hello'"
$ $A
hello': -c: line 0: unexpected EOF while looking for matching `''
hello': -c: line 1: syntax error: unexpected end of file
When executing $A,
the shell performs word splitting on the value of $A.
It finds these words:
bash
-c
'echo
hello'
You probably expected that 'echo hello' would be evaluated to the value echo hello (without the single-quotes), but that is not the case.
It cannot happen.
When you set the value of A with A="...",
any '...' embedded within the "..." are not evaluated,
so the literal ' remain in place.
It would be dangerous to recursively evaluate string values like that.
So after the word splitting, what gets executed looks more like this:
bash -c "'echo" "hello'"
So then bash tries to execute the command 'echo, and it fails,
because it doesn't find a matching '.
As for what is the hello': prefix of the error messages,
I believe this extract from man bash explains:
SYNOPSIS
bash [options] [command_string | file]
...
-c If the -c option is present, then commands are read from the
first non-option argument command_string. If there are argu-
ments after the command_string, the first argument is
assigned to $0 and any remaining arguments are assigned to
the positional parameters. The assignment to $0 sets the
name of the shell, which is used in warning and error mes-
sages.
As another example, in the original code in the question,
the error messages were prefixed with [:, because after the word splitting of "bash -c 'if [ -d / ]; then echo a; else echo b; fi'",
the next word after 'if is [.
If you really want to do something like this, you could use an array instead:
A=(bash -c 'if [ -d / ]; then echo a; else echo b; fi')
"${A[#]}"
By writing this way, A will have 3 values:
bash
-c
if [ -d / ]; then echo a; else echo b; fi
And these will be used as 3 words when executing,
producing the expected output a.
I am making a simple bash script but it seems that i'm having trouble using the let statement... getting some errors... Some help?
#!/bin/bash
echo -n "Enter the first number:"
read var1
echo -n "Enter the second nubmber:"
read var2
declare -i var3
echo ----------------------
echo "$var1 + $var2 = $(($var1+$var2))"
let res=$var1*$var2
echo "$var1 * $var2"=$res"
I'm getting the following errors:
./1.sh: line 10: unexpected EOF while looking for matching `"'
./1.sh: line 11: syntax error: unexpected end of file
They're caused by the last character of the script: " which starts a new string literal that never ends.
I need to write a shell script that for each file from the command line will output the number of words that are longer than a number k read from keyboard and he output must be ordered by the number of words. I don't know if what I've written so far solves the problem because I get these errors and I don't know how to fix them:
./b.sh: command substitution: line 19: unexpected EOF while looking for matching `''
./b.sh: command substitution: line 20: syntax error: unexpected end of file
./b.sh: command substitution: line 19: unexpected EOF while looking for matching `''
./b.sh: command substitution: line 20: syntax error: unexpected end of file
./b.sh: line 19: 5: command not found
#!/bin/bash
case $# in
0)
echo "The script cannot be executed due to missing arguments!"
exit 1
;;
*)
read -p "Enter the length: " k
n=$k
for file in $#; do
if [ `file $file | egrep "exec|data|empty|reloc|cannot open" > /dev$
continue
else
var=`tr ' ' '\n' < $file | grep -c '^.\{`expr $n`\}$'`
echo $file": "$var
fi
done | sort -n
;;
esac
Like the error message tells you, you are missing the pair for a ` delimiter. Also, you have attempted to nest backticks, but done it incorrectly.
In more detail,
#!/bin/bash
case $# in
0)
echo "The script cannot be executed due to missing arguments!"
exit 1
;;
(Strictly, error messages should go to stderr; echo "error" >&2. Also, it is good form to include $0 in the error message; echo "$0: error" >&2 so that you can tell from nested scripts which one is having a problem.)
*)
read -p "Enter the length: " k
n=$k
(Why do you need to copy the value to n?)
for file in $#; do
(This should properly use "$#" in order for file names with spaces etc to work correctly.)
if [ `file $file | egrep "exec|data|empty|reloc|cannot open" > /dev$
continue
You are mixing syntax here. The general syntax is if command; then action; fi. One commonly used command is [ in which case its argument list must end with a closing ], but here, it seems that if should simply examine the exit code from grep. Also, /dev$ looks like an erroneous transcription. Assuming this was meant to be /dev/null, better simply use grep -q.
Also, this is where you have a backtick which starts a command sequence, but no closing backtick to end it.
Because "cannot open" is an error message, it wil not be in standard output. Either copy stderr to stdout, or handle separately. One option is to invert the logic, so that both a missing file and the absence of the strings you are looking for translates to failure.
So,
if ! file "$file" | grep -vEq "exec|data|empty|reloc"; then
continue
Notice also how "$file" needs to be double quoted, again to cope correctly with file names containing wildcard characters, whitespace, etc.
else
var=`tr ' ' '\n' < $file | grep -c '^.\{`expr $n`\}$'`
This is where the nesting is supposed to happen, but your syntax is incorrect. echo `echo foo`echo `echo bar` evaluates to echo fooecho bar, and nested evaluation would be echo `echo foo\`echo \`echo bar` -- or more readably and portably, use echo $(echo foo$(echo )echo bar) where the nesting is obvious and unambiguous. But anyway, your expr is completely superfluous; just interpolate the value if $n (or $k!) directly. So;
var=$(tr ' ' '\n` <"$file" | grep -c "^\{$k\}")
The rest if the script looks correct.
echo $file": "$var
fi
done | sort -n
;;
esac
I need to validate my log files:
-All new log lines shall start with date.
-This date will respect the ISO 8601 standard. Example:
2011-02-03 12:51:45,220Z -
Using shell script, I can validate it looping on each line and verifying the date pattern.
The code is below:
#!/bin/bash
processLine(){
# get all args
line="$#"
result=`echo $line | egrep "[0-9]{4}-[0-9]{2}-[0-9]{2} [012][0-9]:[0-9]{2}:[0-9]{2},[0-9]{3}Z" -a -c`
if [ "$result" == "0" ]; then
echo "The log is not with correct date format: "
echo $line
exit 1
fi
}
# Make sure we get file name as command line argument
if [ "$1" == "" ]; then
echo "You must enter a logfile"
exit 0
else
file="$1"
# make sure file exist and readable
if [ ! -f $file ]; then
echo "$file : does not exists"
exit 1
elif [ ! -r $file ]; then
echo "$file: can not read"
exit 2
fi
fi
# Set loop separator to end of line
BAKIFS=$IFS
IFS=$(echo -en "\n\b")
exec 3<&0
exec 0<"$file"
while read -r line
do
# use $line variable to process line in processLine() function
processLine $line
done
exec 0<&3
# restore $IFS which was used to determine what the field separators are
IFS=$BAKIFS
echo SUCCESS
But, there is a problem. Some logs contains stacktraces or something that uses more than one line, in other words, stacktrace is an example, it can be anything. Stacktrace example:
2011-02-03 12:51:45,220Z [ERROR] - File not found
java.io.FileNotFoundException: fred.txt
at java.io.FileInputStream.<init>(FileInputStream.java)
at java.io.FileInputStream.<init>(FileInputStream.java)
at ExTest.readMyFile(ExTest.java:19)
at ExTest.main(ExTest.java:7)
...
will not pass with my script, but is valid!
Then, if I run my script passing a log file with stacktraces for example, my script will failed, because it loops line by line.
I have the correct pattern and I need to validade the logger date format, but I don't have wrong date format pattern to skip lines.
I don't know how I can solve this problem. Does somebody can help me?
Thanks
You need to anchor your search for the date to the start of the line (otherwise the date could appear anywhere in the line - not just at the beginning).
The following snippet will loop over all lines that do not begin with a valid date. You still have to determine if the lines constitute errors or not.
DATEFMT='^[0-9]{4}-[0-9]{2}-[0-9]{2} [012][0-9]:[0-9]{2}:[0-9]{2},[0-9]{3}Z'
egrep -v ${DATEFMT} /path/to/log | while read LINE; do
echo ${LINE} # did not begin with date.
done
So just (silently) discard a single stack trace. In somewhat verbose bash:
STATE=idle
while read -r line; do
case $STATE in
idle)
if [[ $line =~ ^java\..*Exception ]]; then
STATE=readingexception
else
processLine "$line"
fi
;;
readingexception)
if ! [[ $line =~ ^' '*'at ' ]]; then
STATE=idle
processLine "$line"
fi
;;
*)
echo "Urk! internal error [$STATE]" >&2
exit 1
;;
esac
done <logfile
This relies on processLine not continuing on error, else you will need to track a tad more state to avoid two consecutive stack traces.
This makes 2 assumptions.
lines that begin with whitespace are continuations of previous lines. we're matching a leading space, or a leading tab.
lines that have non-whitespace characters starting at ^ are new log lines.
If a line matching #2 doesn't match the date format, we have an error, so print the error, and include the line number.
count=0
processLine() {
count=$(( count + 1 ))
line="$#"
result=$( echo $line | egrep '^[0-9]{4}-[0-9]{2}-[0-9]{2} [012][0-9]:[0-9]{2}:[0-9]{2},[0-9]{3}Z' -a -c )
if (( $result == 0 )); then
# if result = 0, then my line did not start with the proper date.
# if the line starts with whitespace, then it may be a continuation
# of a multi-line log entry (like a java stacktrace)
continues=$( echo $line | egrep "^ |^ " -a -c )
if (( $continues == 0 )); then
# if we got here, then the line did not start with a proper date,
# AND the line did not start with white space. This is a bad line.
echo "The line is not with correct date format: "
echo "$count: $line"
exit 1
fi
fi
}
Create a condition to check if the line starts with a date. If not, skip that line as it is part of a multi-line log.
processLine(){
# get all args
line="$#"
result=`echo $line | egrep "[0-9]{4}-[0-9]{2}-[0-9]{2} [012][0-9]:[0-9]{2}:[0-9]{2},[0-9]{3}Z" -a -c`
if [ "$result" == "0" ]; then
echo "Log entry is multi-lined - continuing."
fi
}