Scripting in gdb - gdb

Say, for example, I have a C source file with a method like foo(a) where a is a character.
I want to print the output of foo for every character is there an easier way than going through systematically and entering p foo('a') then p foo('b')?
Ideally I'd really like to script it so it's a bit quicker.

I managed to figure it out, my code was basically:
define foo_test
set $a = 97
set $b = 123
while $a < $b
p (char)foo($a)
set $a = $a + 1
end
end

perl -e 'foreach $i ("a" .. "z") { print "print foo('\''$i'\'')\n"; }' > /tmp/t.$$ &&
gdb --batch -x /tmp/t.$$ ./a.out ; rm -f /tmp/t.$$
You should also look into GDB Python scripting.

It sounds like the first thing you should add is some "Breakpoint command lists",
those will let you run some gdb commands after a breakpoint has hit.
So if you add so your print runs when someone calls the functions foo,
you should be are getting kind of close.

Related

shell wrapper to restart script based on its output

I've had a bit of shell scripting practice reading piped input from other programs, but am unsure how to approach this problem.
THE BACKSTORY
A program robinbotter whose internals I can't really fix/modify takes its input from files equities.sym and blacklist.sym, each simple text files containing one ticker symbol per line.
When it runs okay, its output produces:
...
Downloading instruments: [ AFL KELYB LFUS ]
...
When it breaks due to internal bugs,
...
Downloading instruments: [ AFL KELYB LFUS LNVGY
and halts there, with no further output, yielding exit code 0 like in the okay case (unfortunately).
The ticker symbols are printed out with slight delay--no newlines in between--while the program is processing them.
When it hits LNVGY or unpredictably any other many possibilities, somehow it can't handle or at least skip them, instead crashing with no proper exception nor error code.
THE QUESTION
I'm trying to write a minimalistic wrapper script in BASH (eg. retryRB.sh ./robinbotter) which:
Somehow monitors the live unbuffered output of robinbotter, using a regex or other method to detect when output of a line containing "Downloading instruments: [ " doesn't end with "]" before the program ends. In which case:
Take the last symbol printed out (eg. LNVGY) which crashes the program, and append it to the bottom of file blacklist.sym. Like with
echo $lastSymbol >> blacklist.sym
Restart the program robinbotter, retaining its original command-line parameters: $#
I am familiar with tools like awk and sed, and would be open to building a short solution in Ruby if Bash doesn't cut it.
Here you have a Bash version of a code that imitates what your binary does.
then you have a wrapper which logs when the apps successfully completes, and also, when it fails. On failure, it also appends the last item printed, as you can see in the images below ( in this case Im hard-coding a failure on Bomb! and Boom! but you get the idea):
main.sh
#!/bin/bash
some=('Pera' 'Manzana' 'Frutilla' 'Durazno' 'Banana' 'Lechuga' 'Sandia' 'Papa' 'Melon' 'Milanesa' 'Bomb!' 'Boom!')
printf 'Downloading instruments: [ '
for (( i=1 ; i < 5 ; i++ )) {
item=${some[$( shuf -i 0-$(( ${#some[#]} - 1 )) -n 1 )]}
printf "$item"
[[ $item == 'Bomb!' || $item == "Boom!" ]] && exit || printf "$item"
[[ $i -lt 4 ]] && printf ' '
}
printf ' ]'
wrapper.sh
#!/bin/bash
while :
do
res=$( ./main.sh )
[[ ! "$res" =~ \[[^]]*\] ]] && printf "Failure : ${res##*[\[ ]}" || printf "Success"
printf '\n'
sleep 1
done
You can test these scripts and then put your binary in place of main.sh.
Regards!

Bash: Replace array value with curl result

I have a text file named raw.txt with something like the following:
T DOTTY CRONO 52/50 53/40 54/30 55/20 RESNO NETKI
U CYMON DENDU 51/50 52/40 53/30 54/20 DOGAL BEXET
V YQX KOBEV 50/50 51/40 52/30 53/20 MALOT GISTI
W VIXUN LOGSU 49/50 50/40 51/30 52/20 LIMRI XETBO
X YYT NOVEP 48/50 49/40 50/30 51/20 DINIM ELSOX
Y DOVEY 42/60 44/50 47/40 49/30 50/20 SOMAX ATSUR
Z SOORY 43/50 46/40 48/30 49/20 BEDRA NERTU
A DINIM 51/20 52/30 50/40 47/50 RONPO COLOR
B SOMAX 50/20 51/30 49/40 46/50 URTAK BANCS
C BEDRA 49/20 50/30 48/40 45/50 VODOR RAFIN
D ETIKI 48/15 48/20 49/30 47/40 44/50 BOBTU JAROM
E 46/40 43/50 42/60 DOVEY
F 45/40 42/50 41/60 JOBOC
G 43/40 41/50 40/60 SLATN
I'm reading it into an array:
while read line; do
set $line
IFS=' ' read -a array <<< "$line"
done < raw.txt
I'm trying to replace all occurrences of [A-Z]{5} with an curl result where the match of [A-Z]{5} is fed as a variable into the curl call.
First match to be replaced would be DOTTY. The call looks similar to curl -s http://example.com/api_call/DOTTY and the result is something like -55.5833 50.6333 which should replace DOTTY in the array.
I was so far unable to correctly match the desired string and feed the match into curl.
Your help is greatly appreciated.
All the best,
Chris
EDIT:
Solution
Working solution based on #Kevin extensive answer and #Floris hint about a possible carriage return in the curl result. This was indeed the case. Thank you! Combined with some tinkering on my side I now got it to work.
#!/bin/bash
while read line; do
set $line
IFS=' ' read -a array <<< "$line"
i=0
for str in ${array[#]}; do
if [[ "$str" =~ [A-Z]{5} ]]; then
curl_tmp=$(curl -s http://example.com/api_call/$str)
# cut off line break
curl=${curl_tmp/$'\r'}
# insert at given index
declare array[$i]="$curl"
fi
let i++
done
# write to file
for index in "${array[#]}"; do
echo $index
done >> $WORK_DIR/nats.txt
done < raw.txt
I didn't change anything about your script except add the matching part, since it seems that's what you're needing help on:
#!/bin/bash
while read line; do
set $line
IFS=' ' read -a array <<< "$line"
for str in ${array[#]}; do
if [[ "$str" =~ [A-Z]{5} ]]; then
echo curl "http://example.com/api_call/$str"
fi
done
done < raw.txt
EDIT: added in the url example you provided with the variable in the URI. You can do whatever you need with the fetched output by changing it to do_something "$(curl ...)"
EDIT2: Since you're wanting to maintain the bash array you create from each line, how about this:
I'm not great at bash when it comes to arrays, so I expect someone to call me out on it, but this should work.
I've left some echos there so you can see what it's doing. The shift commands are to push the array index from the current location when the regex matches. The tmp variable to hold your curl output could probably be improved, but this should get you started, I hope.
removed temporarily to avoid confusion
EDIT3: Oops the above didn't actually work. My mistake. Let me try again here.
EDIT4:
#!/bin/bash
while read line; do
set $line
IFS=' ' read -a array <<< "$line"
i=0
# echo ${array[#]} below is just so you can see it before processing. You can remove this
echo "Array before processing: ${array[#]}"
for str in ${array[#]}; do
if [[ "$str" =~ [A-Z]{5} ]]; then
# replace the echo command below with your curl command
# ie - curl="$(curl http://example.com/api_call/$str)"
curl="$(echo 1234 -1234)"
if [[ "$flag" = "1" ]]; then
array=( ${adjustedArray[#]} )
push=$(( $push + 2 ));
let i++
else
push=1
fi
adjustedArray=( ${array[#]:0:$i} ${curl[#]} ${array[#]:$(( $i + $push)):${#array[#]}} )
#echo "DEBUG adjustedArray in loop: ${adjustedArray[#]}"
flag=1;
fi
let i++
done
unset flag
echo "final: ${adjustedArray[#]}"
# do further processing here
done < raw.txt
I know there's a smarter way to do this than the above, but we're getting into areas in bash where I'm not really suited to give advice. The above should work, but I'm hoping someone can do better.
Hope it helps, anyway
ps - You should probably not use a shell script for this unless you really need to. Perl, php, or python would make the code simple and readable
Since I misread the first time:
How about just using sed?
sed "s/\([A-Z]\{5\}\)/$(echo curl http:\\/\\/example.com\\/api_call\\/\\1)/g" /tmp/raw.txt
Try that, then try removing the echo. I'm not 100% on this since I can't run it on the real domain
EDIT: And just so I'm clear, the echo is just there so you can see what it will do with the echo removed
create a file cmatch:
#!/bin/bash
while read line
do
echo $line
a=`echo $line | egrep -o '\b[A-Z]{5}\b'`
for v in $a
do
echo "doing curl to replace $v in $line"
r=`curl -s http://example.com/api_call/$v`
r1=`echo $r | xargs echo`
line=`echo $line | sed 's/'$v'/'$r1'/'`
done
done
then call it with
chmod 755 cmatch
./cmatch < inputfile.txt > outputfile.txt
It will do what you asked
Notes:
the \b before and after the [A-Z]{5} ensures that ABCDEFG (which is not a five letter word) will not match.
using egrep -o produces an array of matches
I loop over this array to allow the replacement of multiple matches in a line
I update the line for each match found using the result of the curl call
to keep code clean, I assign the result of the curl to an intermediate variable
edit Just saw the comments about arrays. I suggest to take the output of this script and convert it to an array if you want to do further manipulation...
more edits If your curl command returns a multi-line string (which would explain the error you see), you can use the new line I introduced in the script to remove the newlines (essentially stringing all the arguments together):
echo $r | xargs echo
calls echo with one line at a time as argument, and without the carriage returns. It's a fun way of getting rid of carriage returns.
#!/bin/bash
while read line;do
set -- $line
echo "second parm is $2"
echo "do your curl here"
done < afile.txt

How to extract two numbers from a word and store then in two separate variables in bash?

I believe my question is very simple for someone who knows how to use regular expressions, but I am very new at it and I can't figure out a way to do it. I found many questions similar to this, but none could solve my problem.
In bash, i have a few variables that are of the form
nw=[:digit:]+.a=[:digit:]+
for example, some of these are nw=323.a=42 and nw=90.a=5
I want to retrieve these two numbers and put them in the variables $n and $a.
I tried several tools, including perl, sed, tr and awk, but couldn't get any of these to work, despite I've been googling and trying to fix it for an hour now. tr seems to be the fittest though.
I'd like a piece of code which would achieve the following:
#!/bin/bash
ldir="nw=64.a=2 nw=132.a=3 nw=4949.a=30"
for dir in $ldir; do
retrieve the number following nw and place it in $n
retrieve the number following a and place it in $a
done
... more things...
If you trust your input, you can use eval:
for dir in $ldir ; do
dir=${dir/w=/=} # remove 'w' before '='
eval ${dir/./ } # replace '.' by ' ', evaluate the result
echo $n, $a # show the result so we can check the correctness
done
if you do not trust your input :) use this:
ldir="nw=64.a=2 nw=132.a=3 nw=4949.a=30"
for v in $ldir; do
[[ "$v" =~ ([^\.]*)\.(.*) ]]
declare "n=$(echo ${BASH_REMATCH[1]}|cut -d'=' -f2)"
declare "a=$(echo ${BASH_REMATCH[2]}|cut -d'=' -f2)"
echo "n=$n; a=$a"
done
result in:
n=64; a=2
n=132; a=3
n=4949; a=30
for sure there are more elegant ways, this is just a quick working hack
ldir="nw=64.a=2 nw=132.a=3 nw=4949.a=30"
for dir in $ldir; do
#echo --- line: $dir
for item in $(echo $dir | sed 's/\./ /'); do
val=${item#*=}
name=${item%=*}
#echo ff: $name $val
let "$name=$val"
done
echo retrieve the number following nw and place it in $nw
echo retrieve the number following a and place it in $a
done

How to extract a substring matching a pattern from a Unix shell variable

I'm relatively new to Unix shell scripting. Here's my problem. I've used this script...
isql -S$server -D$database -U$userID -P$password << EOF > $test
exec MY_STORED_PROC
go
EOF
echo $test
To generate this result...
Msg 257, Level 16, State 1:
Server 'MY_SERVER', Procedure 'MY_STORED_PROC':
Implicit conversion from datatype 'VARCHAR' to 'NUMERIC' is not allowed. Use
the CONVERT function to run this query.
(1 row affected)
(return status = 257)
Instead of echoing the isql output, I would like to extract the "257" and stick it in another variable so I can return 257 from the script. I'm thinking some kind of sed or grep command will do this, but I don't really know where to start.
Any suggestions?
bash can strip parts from the content of shell variables.
${parameter#pattern} returns the value of $parameter without the part at the beginning that matches pattern.
${parameter%pattern} returns the value of $parameter without the part at the end that matches pattern.
I guess there is a better way to do this, but this should work.
So you could combine this into:
% strip the part before the value:
test=${test#Msg }
% strip the part after the value:
test=${test%, Level*}
echo $test
If you're interested in the (return status = xxx) part, it would be:
result=${test#*(result status = }
result=${result%)*}
echo $result
The relevant section of the bash manpage is "Parameter Expansion".
Here is a quick and dirty hack for you, though you should really start learning this stuff yourself:
RC=`tail -1 $test |sed 's/(return status = \([0-9]\+\))/\1/'`

How can I extract lines of text from a file?

I have a directory full of files and I need to pull the headers and footers off of them. They are all variable length so using head or tail isn't going to work. Each file does have a line I can search for, but I don't want to include the line in the results.
It's usually
*** Start (more text here)
And ends with
*** Finish (more text here)
I want the file names to stay the same, so I need to overwrite the originals, or write to a different directory and I'll overwrite them myself.
Oh yeah, it's on a linux server of course, so I have Perl, sed, awk, grep, etc.
Try the flip flop! ".." operator.
# flip-flop.pl
use strict;
use warnings;
my $start = qr/^\*\*\* Start/;
my $finish = qr/^\*\*\* Finish/;
while ( <> ) {
if ( /$start/ .. /$finish/ ) {
next if /$start/ or /$finish/;
print $_;
}
}
U can then use the -i perl switch to update your file(s) like so.....
$ perl -i'copy_*' flip-flop.pl data.txt
...which changes data.txt but makes a copy beforehand as "copy_data.txt".
GNU coreutils are your friend...
csplit inputfile %^\*\*\* Start%1 /^\*\*\* Finish/ %% {*}
This produces your desired file as xx00. You can change this behaviour through the options --prefix, --suffix, and --digits, but see the manual for yourself. Since csplit is designed to produce a number of files, it is not possible to produce a file without suffix, so you will have to do the overwriting manually or through a script:
csplit $1 %^\*\*\* Start%1 /^\*\*\* Finish/ %% {*}
mv -f xx00 $1
Add loops as you desire.
To get the header:
cat yourFileHere | awk '{if (d > 0) print $0} /.*Start.*/ {d = 1}'
To get the footer:
cat yourFileHere | awk '/.*Finish.*/ {d = 1} {if (d < 1) print $0}'
To get the file from header to footer as you want:
cat yourFileHere | awk '/.*Start.*/ {d = 1; next} /.*Finish.*/ {d = 0; next} {if (d > 0) print $0}'
There's one more way, with csplit command, you should try something like:
csplit yourFileHere /Start/ /Finish/
And examine files named 'xxNN' where NN is running number, also take a look at csplit manpage.
Maybe? Start to Finish with not-delete.
$ sed -i '/^\*\*\* Start/,/^\*\*\* Finish/d!' *
or...less sure of it...but, if it works, should remove the Start and Finish lines as well:
$ sed -i -e '/./,/^\*\*\* Start/d' -e '/^\*\*\* Finish/,/./d' *
d! may depend on the build of sed you have -- not sure.
And, I wrote that entirely on (probably poor) memory.
A quick Perl hack, not tested. I am not fluent enough in sed or awk to get this effect with them, but I would be interested in how that would be done.
#!/usr/bin/perl -w
use strict;
use Tie::File;
my $Filename=shift;
tie my #File, 'Tie::File', $Filename or die "could not access $Filename.\n";
while (shift #File !~ /^\*\*\* Start/) {};
while (pop #File !~ /^\*\*\* Finish/) {};
untie #File;
Some of the examples in perlfaq5: How do I change, delete, or insert a line in a file, or append to the beginning of a file? may help. You'll have to adapt them to your situation. Also, Leon's flip-flop operator answer is the idiomatic way to do this in Perl, although you don't have to modify the file in place to use it.
A Perl solution that overwrites the original file.
#!/usr/bin/perl -ni
if(my $num = /^\*\*\* Start/ .. /^\*\*\* Finish/) {
print if $num != 1 and $num + 0 eq $num;
}