Run file until the output matches regular expression - regex

I would like to write a bash expression that would run the file "a.out" until the output of the file is equal to "b\na" where "\n" is a newline.

Here you go:
#/bin/bash
a.out | while :
do
read x
read y
[[ $x == 'b' && $y == 'a' ]] && break
echo $x $y
done
Tested this in bash on Ubuntu 13.04.

This might also help you quantify your results:
let ab=0 ba=0
for (( i=0; i<1000; ++i )); do
case "$(./a.out)" in
$'a\nb') let ab+=1;;
$'b\na') let ba+=1;;
esac
done
echo "a\\nb: $ab times; b\\na: $ba times"

Tested on Ubuntu 13.04
pcregrep matches b\na.
the -m flag to grep causes it to exit on first match.
until ./a.out | pcregrep -M 'b\na' | grep -m 1 a; do :; done

This should work:
./a.out | while read line; do
    [[ $s == 1 && $line == 'a' ]] && break
    s=0 
    [[ $line == 'b' ]] && s=1 
done 
Overkill way:
mkfifo myfifo
./a.out > myfifo &
pp=$!
while read line; do
[[ $s == 1 && $line == 'a' ]] && break
s=0
[[ $line == 'b' ]] && s=1
done < myfifo
kill $pp
rm myfifo

Something like
./a.out | sed '/^b$/!b;:l;n;/^b$/bl;/^a$/q'
Translation: if the current input line does not match ^b$ (beginning of line, b, end of line) start over with the next input line; otherwise, fetch the next input line; as long as we get another ^b$, keep reading, otherwise, if it matches ^a$, stop reading and quit.
:l declares a label so we have somewhere to go back to in the while loop. b without an explicit label branches to the end of the script (which then starts over with the next input line).

Related

Negative to positive number range in regex in bash scripting [duplicate]

I want to just insert number between two values, and otherwise the script repeated until correct number.
This is my script and it does not work correctly:
validation(){
read number
if [ $number -ge 2 && $number -ls 5 ]; then
echo "valid number"
break
else
echo "not valid number, try again"
fi
}
echo "insert number"
validation
echo "your number is" $number
If you are using Bash, you are better off using the arithmetic expression, ((...)) for readability and flexibility:
if ((number >= 2 && number <= 5)); then
# your code
fi
To read in a loop until a valid number is entered:
#!/bin/bash
while :; do
read -p "Enter a number between 2 and 5: " number
[[ $number =~ ^[0-9]+$ ]] || { echo "Enter a valid number"; continue; }
if ((number >= 2 && number <= 5)); then
echo "valid number"
break
else
echo "number out of range, try again"
fi
done
((number >= 2 && number <= 5)) can also be written as ((2 <= number <= 5)).
See also:
Test whether string is a valid integer
How to use double or single brackets, parentheses, curly braces
Your if statement:
if [ $number -ge 2 && $number -ls 5 ]; then
should be:
if [ "$number" -ge 2 ] && [ "$number" -le 5 ]; then
Changes made:
Quoting variables is considered good practice.
ls is not a valid comparison operator, use le.
Separate single-bracket conditional expressions with &&.
Also you need a shebang in the first line of your script: #!/usr/bin/env bash
if [ $number -ge 2 && $number -ls 5 ]; then
should be
if [[ $number -ge 2 && $number -le 5 ]]; then
see help [[ for details
Try bellow code
echo "Enter number"
read input
if [[ $input ]] && [ $input -eq $input 2>/dev/null ]
then
if ((input >= 1 && input <= 4)); then
echo "Access Granted..."
break
else
echo "Wrong code"
fi
else
echo "$input is not an integer or not defined"
fi
2 changes needed.
Suggested by Sergio.
if [ "$number" -ge 2 ] && [ "$number" -le 5 ]; then
There is no need of break. only meaningful in a for, while, or until loop
while :; do
read option
if [[ $option -ge 1 && $option -lt 4 ]]; then
echo "correct"
c
break
else
echo "Incorrect option selected,choose an option between [1-4]"
fi
done

How should I use exact keyword matching as a condition in the case statement?

I was trying to write myself some handy scripts in order to legitimately slacking off work more efficiently, and this question suddenly popped up:
Given a very long string $LONGEST_EVER_STRING and several keywords strings like $A='foo bar' , $B='omg bbq' and $C='stack overflow'
How should I use exact keyword matching as a condition in the case statement?
for word in $LONGEST_EVER_STRING; do
case $word in
any exact match in $A) do something ;;
any exact match in $B) do something ;;
any exact match in $C) do something ;;
*) do something else;;
esac
done
I know I can write in this way but it looks really ugly:
for word in $LONGEST_EVER_STRING; do
if [[ -n $(echo $A | fgrep -w $word) ]]; then
do something;
elif [[ -n $(echo $B | fgrep -w $word) ]]; then
do something;
elif [[ -n $(echo $C | fgrep -w $word) ]]; then
do something;
else
do something else;
fi
done
Does anyone have an elegant solution? Many thanks!
You could use a function to do a little transform in your A, B, C variables and then:
shopt -s extglob
Ax="+(foo|bar)"
Bx="+(omg|bbq)"
Cx="+(stack|overflow)"
for word in $LONGEST_EVER_STRING; do
case $word in
$Ax) do something ;;
$Bx) do something ;;
$Cx) do something ;;
*) do something else;;
esac
done
I would just define a function for this. It'll be slower than grep for large wordlists, but faster than starting up grep many times.
##
# Success if the first arg is one of the later args.
has() {
[[ $1 = $2 ]] || {
[[ $3 ]] && has "$1" "${#:3}"
}
}
$ has a b c && echo t || echo f
f
$ has a b c a d e f && echo t || echo f
t
A variation on /etc/bashrc's "pathmunge"
for word in $LONGEST_EVER_STRING; do
found_word=false
for list in " $A " " $B " " $C "; do
if [[ $list == *" $word "* ]]; then
found_word=true
stuff with $list and $word
break
fi
done
$found_word || stuff when not found
done

How can I check the last character in a string in bash?

I need to ensure that the last character in a string is a /
x="test.com/"
if [[ $x =~ //$/ ]] ; then
x=$x"extention"
else
x=$x"/extention"
fi
at the moment, false always fires.
Like this, for example:
$ x="test.com/"
$ [[ "$x" == */ ]] && echo "yes"
yes
$ x="test.com"
$ [[ "$x" == */ ]] && echo "yes"
$
$ x="test.c/om"
$ [[ "$x" == */ ]] && echo "yes"
$
$ x="test.c/om/"
$ [[ "$x" == */ ]] && echo "yes"
yes
$ x="test.c//om/"
$ [[ "$x" == */ ]] && echo "yes"
yes
You can index strings in Bash using ${var:index} and ${#var} to get the length of the string. Negative indices means the moving from the end to the start of the string so that -1 is index of the last character:
if [[ "${x:${#x}-1}" == "/" ]]; then
# last character of x is /
fi
Your condition was slightly incorrect. When using =~, the rhs is considered a pattern, so you'd say pattern and not /pattern/.
You'd have got expected results if you said
if [[ $x =~ /$ ]] ; then
instead of
if [[ $x =~ //$/ ]] ; then
You can do this generically using bash substrings $(string:offset:length} - length is optional
#x is the length of x
Therefore
$n = 1 # 1 character
last_char = ${x:${#x} - $n}
For future references,
$ man bash
has all the magic
${parameter:offset:length}
Substring Expansion. Expands to up to length characters of parameter
starting at the character specified by offset. If length is
omitted, expands to the substring of parameter starting at the
character specified by offset. length and offset are arithmetic
expressions ...

Bash: need to find text within matching braces (parantheses) in text

I have some text that looks like this:
(something1)something2
However something1 and something2 might also have some parentheses inside them such as
(some(thing)1)something(2)
I want to extract something1 (including internal parentheses if there are any) to a variable. Since I can count on the text always starting with an opening parentheses, I'm hoping that I can do something where I match the first parenthesis to the correct closing parentheses, and extract the middle.
Everything I have tried so far has the potential to match the wrong ending parentheses.
If you have perl, the:
perl -MText::Balanced -nlE 'say [Text::Balanced::extract_bracketed( $_, "()" )]->[0]' <<EOF
(something1)something2
(some(thing)1)something(2)
(some(t()()hing)()1)()something(2)
EOF
will prints
(something1)
(some(thing)1)
(some(t()()hing)()1)
Since this is apparently something that is impossible with regular expressions, I have resorted to pickup the the characters 1 by 1:
first=""
count=0
while test -n "$string"
do
char=${string:0:1} # Get the first character
if [[ "$char" == ")" ]]
then
count=$(( $count - 1 ))
fi
if [[ $count > 0 ]]
then
first="$first$char"
fi
if [[ "$char" == "(" ]]
then
count=$(( $count + 1 ))
fi
string=${string:1} # Trim the first character
if [[ $count == 0 ]]
then
second="$string"
string=""
fi
done
You can do it with perl:
echo "(some(thing)1)something(2)" | perl -ne '$_ =~ /(\((?:\(.*\)|[^(])*\))|\w+/s; print $1;'
awk can do it:
#!/bin/awk -f
{
for (i=1; i<=length; ++i) {
if (numLeft == 0 && substr($0, i, 1) == "(") {
leftPos = i
numLeft = 1
} else if (substr($0, i, 1) == "(") {
++numLeft
} else if (substr($0, i, 1) == ")") {
++numRight
}
if (numLeft && numLeft == numRight) {
print substr($0, leftPos, i-leftPos+1)
next
}
}
}
Input:
(something1)something2
(some(thing)1)something(2)
Output:
(something1)
(some(thing)1)

How can I check the balance of ASCII images using bash?

I have some large ASCII images that I want to check are symmetrical. Say I have the following file:
***^^^MMM
*^**^^MMM
**^^^^^MMMMM
The first line is what I want, they are all separated and have the same amount in each section (it doesn't have to be 3 of each ever time though), and the next two are not what I want. I want to count the number of *'s in a row, and then make sure there are the same amount of ^'s and M's following them. I'm trying to get some symmetry on each line, so this would be good:
**^^MM
**********^^^^^^^^^^MMMMMMMMMM
****^^^^MMMM
*^M
etc.
How can I scan through a file and maybe grep the problem lines?
I tried a few loops with cat ASCIIfile | sed 's/\^//g' | sed 's/M//g' | wc -c and assigning output to a variable and then comparing the count to the other char counts, but obviously this doesn't take into account order and lines like *^*^*M^MM were working.
Using perl:
perl -ne ' { $l=$_; chomp; ($v)=/^((.)\2*)/; $t=length($v); \
s/M{$t}//;s/\^{$t}//;s/\*{$t}//; \
print $l if length } ' input_file
Using bash/sed:
while read line; do
m=$(echo $line | sed 's/[^M]*\([M][M]*\)[^M]*/\1/' | wc -c)
s=$(echo $line | sed 's/[^*]*\([*][*]*\)[^*]*/\1/' | wc -c)
n=$(echo $line | sed 's/[^\^]*\([\^][\^]*\)[^\^]*/\1/' | wc -c)
if [[ $m -ne $s || $m -ne $n ]]; then
echo "- $line $m::$s::$n"
else
echo "+ $line $m::$s::$n"
fi
done < input_file
Pure Bash:
#!/bin/bash
for string in '***^^^MMM' '**^^MM' '****^^MMMM' '*^*M^'
do
flag=true
sym=true
patt=''
prevlen=${#string}
for c in '*' '^' 'M'
do
patt+="*\\$c"
sub="${string##$patt}"
sublen="${#sub}"
if $flag
then
flag=false
((compare = prevlen - sublen ))
else
if (( prevlen - sublen != compare ))
then
printf '%s\n' "$string is NOT symmetrical"
sym=false
break
fi
fi
prevlen=$sublen
done
if $sym
then
printf '%s\n' "$string IS symmetrical"
fi
done
To read from a file, change the first for loop to while read -r string and add < filename after the last done on the same line.