script in bash two if condition and print in for loop - bash

Hello im learning to script in bash, i trying to solve a little exercise.
The Exercise is this
If the variable named "basenew" contains the contents of the variable
named "valuebase". "basenew" must contain more than 113,469
characters. If both conditions are met, the script must then print
the last 20 characters of the variable "basenew".
My code is
#!/bin/bash
basenew="8dm7KsjU28B7v621Jls"
valuebase="ERmFRMVZ0U2paTlJYTkxDZz09Cg"
for i in {1..40}
do
basenew=$(echo $basenew | base64)
if [[ $basenew =~ $valuebase && ${#basenew} -ge 113469 ]] ; then
echo $i
echo $basenew | wc -c
StrLen=`echo ${basenew} | wc -c`
From=`expr $StrLen - 20`
echo $basenew | cut -c ${From}-${StrLen}
else
echo "error"
fi
done
But im stuck, because prints in the loop 28, and are the 20 last but isn't the correct answer.
Any advice to print the last 20 characters using tail -c 20?
Thanks

Why do you think the value of basenew will change during the execution of your loop? You never assign it. But you assign var that you never use, could it be the issue? Yes it is, you need to update basenew, not the unused var.
There is another problem with your script: cut applies your character selection on each line of the input. base64, by default, splits the output in lines of 76 characters. So, after a while, you have many short lines. And as they all are much shorter than your specification cut would only print a lot of newlines, not the last 20 characters of the string. You could change the behaviour of base64 with base64 -w0 but it would also change the results of each iteration and you would probably never match the reference string.
Instead of piping echo in cut follow joop's suggestion: "${basenew: -20: 20}" expands as the 20 last characters of the value of basenew. Or, a bit simpler but equivalent: "${basenew: -20}". Do not forget the space between : and -20, it is needed.
Finally, you could use more built-in bash constructs to avoid external commands (wc, echo, cut), use the modern $(...) instead of the old backticks, and use the arithmetic evaluation (((...))) instead of the obsolete expr. Try, maybe:
#!/usr/bin/env bash
# file: foo.sh
basenew="8dm7KsjU28B7v621Jls"
valuebase="ERmFRMVZ0U2paTlJYTkxDZz09Cg"
for i in {1..40}; do
basenew=$(echo "$basenew" | base64)
if [[ $basenew =~ $valuebase ]] && (( ${#basenew} >= 113469 )) ; then
printf '%d\n%d\n%s\n' "$i" "${#basenew}" "${basenew: -20}"
else
printf 'error\n'
fi
done
Demo:
$ ./foo.sh
error
...
error
28
113469
U2paTlJYTkxDZz09Cg==
error
...
error

Any advice to print the last 20 characters using tail
From the tail man-page:
-c, --bytes=[+]NUM
output the last NUM bytes; or use -c +NUM to output starting with
byte NUM of each file
Hence, if you have a variable x, you get the last 20 characters of it printed to stdout using
tail --bytes=20 <<<$x

Related

continue <n> not skipping <n> iterations forward in shell script

I have created a hex to ASCII converter for strings in bash. The application I'm on changes characters (anything but [0-9],[A-Z],[a-z]) , in a string to its corresponding %hexadecimal. Eg: / changes to %2F in a string
I want to retain the ASCII characters as it is. Below is my code:
NAME=%2fhome%40%21%23
C_NAME=""
for (( i=0; i<${#NAME}; i++ )); do
CHK=$(echo "{NAME:$i:1}" | grep -v "\%" &> /dev/null;echo $?)
if [[ ${CHK} -eq 0 ]]; then
C_NAME=`echo "$C_NAME${NAME:$i:1}"`
else
HEX=`echo "${NAME:$i:3}" | sed "s/%//"`
C_NAME=`echo -n "$C_NAME";printf "\x$HEX"`
continue 2
fi
done
echo "$C_NAME"
OUTPUT:
/2fhome#40!21#23
EXPECTED:
/home#!#
So basically the conversion is happening, but not in place. Its retaining the hex values as well, which tells me the continue 2 statement is probably not working as I expect in my code. Any workarounds please.
You only have one loop so I assume you expected that continue 2 skips the current and next iteration of the current loop, however, the documentation help continue clearly states
continue [n]
[...]
If N is specified, resumes the Nth enclosing loop.
There is no built-in to skip the current and also the next iteration of the current loop, but in your case you can use (( i += 2 )) instead of continue 2.
Using the structure of your script with some simplifications and corrections:
#!/bin/bash
name=%2fhome%40%21%23
c_name=""
for (( i=0; i<${#name}; i++ )); do
c=${name:i:1}
if [[ $c != % ]]; then
c_name=$c_name$c
else
hex=${name:i+1:2}
printf -v c_name "%s\x$hex" "$c_name"
(( i += 2 )) # stolen from Dudi Boy's answer
fi
done
echo "$c_name"
Always use lower case or mixed case variables to avoid the chance of name collisions with shell or environment variables
Always use $() instead of backticks
Most of the echo commands you use aren't necessary
You can avoid using sed and grep
Variables should never be included in the format string of printf but it can't be avoided easily here (you could use echo -e "\x$hex" instead though)
You can do math inside parameter expansions
% doesn't need to be escaped in your grep command
You could eliminate the $hex variable if you used its value directly:
printf -v c_name "%s\x${name:i+1:2}" "$c_name"
I really enjoyed your exercise and decided to solve it with awk (my current study).
Hope you like it as well.
cat script.awk
BEGIN {RS = "%[[:xdigit:]]+"} { # redefine record separtor to RegEx (gawk specific)
decNum = strtonum("0x"substr(RT, 2)); # remove prefix # from record separator, convert hex num to dec
outputStr = outputStr""$0""sprintf("%c", decNum); # reconstruct output string
}
END {print outputStr}
The output
echo %2fhome%40%21%23 |awk -f script.awk
/home#!#

Bash read function returns error code when using new line delimiter

I have a script that I am returning multiple values from, each on a new line. To capture those values as bash variables I am using the read builtin (as recommended here).
The problem is that when I use the new line character as the delimiter for read, I seem to always get a non-zero exit code. This is playing havoc with the rest of my scripts, which check the result of the operation.
Here is a cut-down version of what I am doing:
$ read -d '\n' a b c < <(echo -e "1\n2\n3"); echo $?; echo $a $b $c
1
1 2 3
Notice the exit status of 1.
I don't want to rewrite my script (the echo command above) to use a different delimiter (as it makes sense to use new lines in other places of the code).
How do I get read to play nice and return a zero exit status when it successfully reads 3 values?
Update
Hmmm, it seems that I may be using the "delimiter" wrongly. From the man page:
-d *delim*
The first character of delim is used to terminate the input line,
rather than newline.
Therefore, one way I could achieve the desired result is to do this:
read -d '#' a b c < <(echo -e "1\n2\n3\n## END ##"); echo $?; echo $a $b $c
Perhaps there's a nicer way though?
The "problem" here is that read returns non-zero when it reaches EOF which happens when the delimiter isn't at the end of the input.
So adding a newline to the end of your input will make it work the way you expect (and fix the argument to -d as indicated in gniourf_gniourf's comment).
What's happening in your example is that read is scanning for \ and hitting EOF before finding it. Then the input line is being split on \n (because of IFS) and assigned to $a, $b and $c. Then read is returning non-zero.
Using -d for this is fine but \n is the default delimiter so you aren't changing anything if you do that and if you had gotten the delimiter correct (-d $'\n') in the first place you would have seen your example not work at all (though it would have returned 0 from read). (See http://ideone.com/MWvgu7)
A common idiom when using read (mostly with non-standard values for -d is to test for read's return value and whether the variable assigned to has a value. read -d '' line || [ "$line" ] for example. Which works even when read fails on the last "line" of input because of a missing terminator at the end.
So to get your example working you want to either use multiple read calls the way chepner indicated or (if you really want a single call) then you want (See http://ideone.com/xTL8Yn):
IFS=$'\n' read -d '' a b c < <(printf '1 1\n2 2\n3 3')
echo $?
printf '[%s]\n' "$a" "$b" "$c"
And adding \0 to the end of the input stream (e.g. printf '1 1\n2 2\n3 3\0') or putting || [ "$a" ] at the end will avoid the failure return from the read call.
The setting of IFS for read is to prevent the shell from word-splitting on spaces and breaking up my input incorrectly. -d '' is read on \0.
-d is the wrong thing to use here. What you really want is three separate calls to read:
{ read a; read b; read c; } < <(echo $'1\n2\n3\n')
Be sure that the input ends with a newline so that the final read has an exit status of 0.
If you don't know how many lines are in the input ahead of time, you need to read the values into an array. In bash 4, that takes just a single call to readarray:
readarray -t arr < <(echo $'1\n2\n3\n')
Prior to bash 4, you need to use a loop:
while read value; do
arr+=("$value")
done < <(echo $'1\n2\n3\n')
read always reads a single line of input; the -d option changes read's idea of what terminates a line. An example:
$ while read -d'#' value; do
> echo "$value"
> done << EOF
> a#b#c#
> EOF
a
b
c

Grep a specific string from bash script and compare

i have a large log file A.log, i want to grep ONE CERTAIN STRING from the last 10 lines and compare to a variable (FTP_SUCCESS_MSG), how can i do that?
something like:
logs='/tmp/A.log'
FTP_SUCCESS_MSG="226 Transfer complete"
if [tail -10 $logs == $FTP_SUCCESS_MSG] ;
then
echo "Success"
else
echo "Failed"
exit 1
fi
if tail -10 "$logs" | grep -Fq "$FTP_SUCCESS_MSG" ; then ...
Notice how [ is not present in the condition (and if it were, it would require non-optional spaces on both sides).
Notice also how variable interpolations are in double quotes unless you require the shell to tokenize the value and perform wildcard expansion on the tokens.

test command in KornShell (ksh)

I have a question on the test command in the KornShell (ksh). I know -ne is for comparing integers and != is for comparing strings. How will the test command behave if one argument is a string and the other is an integer? I have below conditions in my code and both are working properly.
Code:
myCount=1
myCount=`expr $myCount+ 0`
temp=`ps -aef | grep damn | wc -l`
if [ $temp -ne $myCount]; then
echo ERROR Number
fi
if [ $temp != $myCount ]; then
echo ERROR Strings
fi
Output:
ERROR Number
ERROR Strings
The type is not relevant because it's a simple text substitution. In other words, the value of the variable $temp will be substituted in place of $temp (for example).
At least for the version of ksh I'm running, for the numeric comparison, if the value starts with a non-numeric, it will equate to 0. If it starts with a numeric but contains non-numerics, you will get an error.
For example:
$ export s1=xyz
$ export s2=7xyz
$ export i1=0
$ if [ $i1 -eq $s1 ]
> then
> echo equal
> fi
equal
$ if [ $i1 -eq $s2 ]
> then
> echo equal
> fi
ksh: 7xyz: bad number `7xyz'
However, based on your comments, that may not be the case for all versions of ksh.
Based on that, I would try to ensure that you use string comparisons for strings and numeric comparisons for numbers. Anything else may be non-portable.
But your code is flawed anyway.
temp=ps -aef | grep damn | wc -l
will always return at least 1, since it will find the grep command as well as being a string padded with leading spaces, which is why both of your tests are true.
Piping to wc is also unnecessary since the -c switch of grep will count for you.
better code would be:
temp=ps -aef |grep damn |grep -cv grep
which will return the number of running instances of processes containing the damn string and it will be a number.
Using ksh93 and GNU coreutils expr 7.4 your command:
myCount=`expr $myCount+ 0`
gives me a syntax error and sets myCount to null which causes both if statements to output "ksh: [: argument expected" errors. Try putting a space before the plus sign. Also, there needs to be a space before ].
You shouldn't need to convert myCount or temp to integers. The coercion of myCount using expr is completely unnecessary.
I prefer this form for comparing integers since it allows you to use symbolic comparison operators such as != and > instead of -ne and -gt:
if (( $temp != $myCount ))

how to grep a variable in the shell program? [duplicate]

This question already has answers here:
grep for expression containing variable
(2 answers)
Closed 9 years ago.
#!/bin/bash
for ((var=0; var<20; var++))
do
echo " Number is: $(grep 'Multiple_Frame = echo **$var**' 20mrf.txt | wc -l)" >>statisic.txt
done
This shell program cannot produce correct result which maybe the reason of wrong variable returning in the second grep command.
How can I grep a variable within the second echo sentence? to grep different things according to the var changing?
Many thanks!
As others have stated, the problem is that the single quotes prevent expansion of the variable. However, using $() allows you to use double quotes:
echo " Number is: $(grep "Multiple_Frame = echo **$var**" 20mrf.txt | wc -l)" >>statisic.txt
although I suspect something like this is what you meant:
echo " Number is: $(grep "Multiple_Frame = $var" 20mrf.txt | wc -l)" >>statisic.txt
You should also be aware that grep has an option to output the count so you can omit wc:
echo " Number is: $(grep -c "Multiple_Frame = $var" 20mrf.txt)" >>statisic.txt
#OP, doing what you do that way is rather inefficient. You are calling grep and wc 20 times on the same file. Open the file just once, and get all the things you want in 1 iteration of the file contents.
Example in bash 4.0
declare -A arr
while read -r line
do
case "$line" in
*"Multiple_Frame ="*)
line=${line#*Multiple_Frame = }
num=${line%% *}
if [ -z ${number_num[$num]} ] ;then
number_num[$num]=1
else
number_num[$num]=$(( number_num[$num]+1 ))
fi
;;
esac
done <"file"
for i in "${!number_num[#]}"
do
echo "Multiple_Frame = $i has ${number_num[$i]} counts"
done
similarly, you can use associative arrays in gawk to help you do this task.
gawk '/Multiple_Frame =/{
sub(/.*Multiple_Frame = /,"")
sub(/ .*/,"")
arr["Multiple_Frame = "$0]=arr["Multiple_Frame = "$0]+1
}END{
for(i in arr) print i,arr[i]
}' file
You have to store each substitution in a variable. Like this:
#!/bin/bash
for ((var=0; var < 20; var++))
do
count=`grep "Multiple_Frame = $var" 20mrf.txt | wc -l`
echo " Number is: $count" >> statisic.txt
done
Ok, the second [sic] problem is with your quoting on line 5. The reference to $var will never be expanded because it's contained within single quotes. You can fix that by replacing the single quotes (') with escaped double quotes (\").
The first [sic] problem is that you're trying to do too much in a single line, which causes your nesting problem with quotes. Break the line up into multiple commands, storing intermediary results as necessary. Yeah, it might run a tad slower, but you'll save a LOT of time debugging and maintaining it.
Trader's Second Law: If you have to choose between optimizing for performance, and optimizing for maintainability, ALWAYS choose to make your code more maintainable. Computers get faster all the time; Programmers don't.
The string inside $(...) is quoted with single quotes ('...') this quoting prevents the variable expansion. Use Double quotes instead. In your case:
#!/bin/bash
for ((var=0; var<20; var++))
do
echo " Number is: $(grep 'Multiple_Frame = echo **'"$var"'**' 20mrf.txt | wc -l)" >>statisic.txt
done
Note that in this example, it seems like the double quotes for the echo command are already opened, but you should note that the $(...) is evaluated first, and there is no double quotes inside it. So the change here is to close the single quote of the grep open double quote instead, and close the double quotes and reopen single quote later.
This lengthy explanation illustrates the benefit of breaking the expression apart, as suggested by other answers.

Resources