Pintos - syscalls project 2 - pintos

I am doing the Pintos project on the side to learn more about operating systems. I finished Project 1 and have started the second project. I already have setup stack verified and working (via hex_dump). Right now I am having issues getting the correct syscall arguments.
In user/syscall.c there are 4 assembly stubs(0 - 4 stubs) that the user syscall wrap.
#define syscall3(NUMBER, ARG0, ARG1, ARG2) \
({ \
int retval; \
asm volatile \
("pushl %[arg2]; pushl %[arg1]; pushl %[arg0]; " \
"pushl %[number]; int $0x30; addl $16, %%esp" \
: "=a" (retval) \
: [number] "i" (NUMBER), \
[arg0] "g" (ARG0), \
[arg1] "g" (ARG1), \
[arg2] "g" (ARG2) \
: "memory"); \
retval; \
}) (this code is given to us)
I have some code inside of my syscall_handler that calls the correct function inside of the kernel.
static void syscall_handler (struct intr_frame *f) {
uint32_t *args = f->esp;
if (args[0] == SYS_WRITE) {
f->eax = write(args);
}
Inside of my write function I am printing out the FD and Size
int sysCallNumber = (int) args[0];
int fd = (int) args[1];
const char *buffer = (char *) args[2];
unsigned size = (unsigned) args[3];
printf("FD is %d\n", fd);
printf("Size is %d\n", size);
Running 'echo hello stack overflow 1 22 333' will yield the result below. Note I added the notes in parentheses. () <- Something is getting screwed up and FD is getting overridden with the size (including null terminator)
FD is 6 (hello)
Size is 6
FD is 6 (stack)
Size is 6
FD is 9 (overflow)
Size is 9
FD is 2 (1)
Size is 2
FD is 3 (22)
Size is 3
FD is 4 (333)
Size is 4
FD is 1 (this is from the printf("\n") in echo.c)
Size is 1
Ive ran this with GDB setting breakpoints and dumping frames and have not been able to figure it out. Has anyone encountered anything similar? If so how did you fix it?
Thanks!

I discovered a bug recently in user/syscall.c that could be affecting you. Try applying this patch to user/syscall.c:
diff --git a/src/lib/user/syscall.c b/src/lib/user/syscall.c
index a9c5bc8..efeb38c 100644
--- a/src/lib/user/syscall.c
+++ b/src/lib/user/syscall.c
## -10,7 +10,7 ##
("pushl %[number]; int $0x30; addl $4, %%esp" \
: "=a" (retval) \
: [number] "i" (NUMBER) \
- : "memory"); \
+ : "memory", "esp"); \
retval; \
})
## -24,7 +24,7 ##
: "=a" (retval) \
: [number] "i" (NUMBER), \
[arg0] "g" (ARG0) \
- : "memory"); \
+ : "memory", "esp"); \
retval; \
})
## -40,7 +40,7 ##
: [number] "i" (NUMBER), \
[arg0] "g" (ARG0), \
[arg1] "g" (ARG1) \
- : "memory"); \
+ : "memory", "esp"); \
retval; \
})
## -57,7 +57,7 ##
[arg0] "g" (ARG0), \
[arg1] "g" (ARG1), \
[arg2] "g" (ARG2) \
- : "memory"); \
+ : "memory", "esp" ); \
retval; \
})
Here's the long story...
In the macro syscall3 for example, the expected assembly code to be generated looks something like this
pushl ARG2
pushl ARG1
pushl ARG0
pushl NUMBER
int $0x30
addl $16, %esp
This should work as long as the stack looks exactly as expected just push the interrupt instruction. Here is what the disassembled write function looks like in of the test programs (generated by objdump -d pintos/src/userprog/build/tests/userprog/args-many):
0804a1a5 <write>:
804a1a5: ff 74 24 0c pushl 0xc(%esp)
804a1a9: ff 74 24 08 pushl 0x8(%esp)
804a1ad: ff 74 24 04 pushl 0x4(%esp)
804a1b1: 6a 09 push $0x9
804a1b3: cd 30 int $0x30
804a1b5: 83 c4 10 add $0x10,%esp
804a1b8: c3 ret
There is an enormous problem with these instructions. Because the arguments are being pushed onto the stack relative to the stack pointer %esp, the wrong arguments are pushed onto the stack! This is because %esp changes after every pushl instruction.
After doing some digging on the extended asm syntax for gcc, I think I found the right fix. The %esp register needs to be added to the Clobbers list in the extended asm instruction for each syscall*. This is because each pushl instruction modifies %esp indirectly, so we need to inform gcc of that modification so that it does not use %esp improperly in the generated instructions.

Related

Bash. Hex to ascii. Is possible without xxd or perl?

I'm developing a script on which I have a hex string 31323334353637383930313233 and I want to transform it into ASCII. Desired output is 1234567890123.
I already have it working using:
echo "31323334353637383930313233" | xxd -r -p
or
echo "31323334353637383930313233" | perl -pe 's/(..)/chr(hex($1))/ge'
But the point is try to use the minimum possible requirements for the script. I want it working in suse, fedora, debian, ubuntu, arch, etc... It seems the xxd command is included in vim package. I'm wondering if there is a way to achieve this using only awk or any internal Linux tool which is going to be present by default in all Linux systems.
Found this script here:
#!/bin/bash
function hex2string () {
I=0
while [ $I -lt ${#1} ];
do
echo -en "\x"${1:$I:2}
let "I += 2"
done
}
hex2string "31323334353637383930313233"
echo
You may change the line hex2string "31323334353637383930313233" so that it takes the hex value from parameters, that is:
#!/bin/bash
function hex2string () {
I=0
while [ $I -lt ${#1} ];
do
echo -en "\x"${1:$I:2}
let "I += 2"
done
}
hex2string "$1"
echo
So when executed as:
./hexstring.sh 31323334353637383930313233
It will provide the desired ascii output.
NOTE: Can't test if it works in all Linux systems.
Using gawk, from HEX to ASCII
$ gawk '{
gsub(/../,"0x& ");
for(i=1;i<=NF;i++)
printf("%c", strtonum($i));
print ""
}' <<<"31323334353637383930313233"
1234567890123
Using any awk
$ cat hex2asc_anyawk.awk
BEGIN{
split("0 1 2 3 4 5 6 7 8 9 A B C D E F", d, / /)
for(i in d)Decimal[d[i]]=i-1
}
function hex2dec(hex, h,i,j,dec)
{
hex = toupper(hex);
i = length(hex);
while(i)
{
dec += Decimal[substr(hex,i,1)] * 16 ^ j++
i--
}
return dec;
}
{
gsub(/../,"& ");
for(i=1;i<=NF;i++)
printf("%d",hex2dec($i));
print ""
}
Execution
$ awk -f hex2asc_anyawk.awk <<<"31323334353637383930313233"
1234567890123
Explanation
Steps :
Get the decimal equivalent of hex from table.
Multiply every digit with 16 power of digit location.
Sum all the multipliers.
Example :
BEGIN{
# Here we created decimal conversion array, like above table
split("0 1 2 3 4 5 6 7 8 9 A B C D E F", d, / /)
for(i in d)Decimal[d[i]]=i-1
}
function hex2dec(hex, h,i,j,dec)
{
hex = toupper(hex); # uppercase conversion if any A,B,C,D,E,F
i = length(hex); # length of hex string
while(i)
{
# dec var where sum is stored
# substr(hex,i,1) gives 1 char from RHS
# multiply by 16 power of digit location
dec += Decimal[substr(hex,i,1)] * 16 ^ j++
i-- # decrement by 1
}
return dec;
}
{
# it modifies record
# suppose if given string is 31323334353637383930313233
# after gsub it becomes 31 32 33 34 35 36 37 38 39 30 31 32 33
# thus re-evaluate the fields
gsub(/../,"& ");
# loop through fields , NF gives no of fields
for(i=1;i<=NF;i++)
# convert from hex to decimal
# and print equivalent ASCII value
printf("%c",hex2dec($i));
# print newline char
print ""
}
Meaning of dec += Decimal[substr(hex,i,1)] * 16 ^ j++
dec += Decimal[substr(hex,i,1)] * 16 ^ j++
^ ^ ^
| | |
| | 2.Multiply every digit with 16 power of digit location.
| |
| 1.Gives decimal equivalent of hex
|
|
3. Sum all the multipliers
here's a special cheating trick for u - due to ingenuity of how they originally mapped decimal digits to bytes, their hex are all x3[0-9],
so therefore, if u already know they would decode out to digits and nothing else, here's a fast shortcut :
echo "31323334353637383930313233" |
mawk 'gsub("..","_&") + gsub("_3",_)^_'
1234567890123
if it's already URL-percent-encoded, then it's even simpler :
echo '%31%32%33%34%35%36%37%38%39%30%31%32%33' |
mawk 'gsub("%3",_)^_'
or
gawk ++NF FS='%3' OFS=
1234567890123
This specialized approach can handle hex of absolutely any arbitrary size, even for awks that don't have built-in support for bigints
TL;DR : don't "do math" when none is needed
Alternate (g)awk solution:
echo "31323334353637383930313233" | awk 'RT{printf "%c", strtonum("0x"RT)}' RS='[0-9]{2}'

KSH measure elapsed time

I am trying to find the day difference from the last time a password was rest to the current. So i have this so far. I am trying to just convert that date to days so i can subtract the current date in days - the last reset date in days and get a integer value.
$ LASTRESETDATE=$(echo $(passwd -s) | cut -d' ' -f3)
$ echo $LASTRESETDATE
12/15/16
Looking the the date verion i have there is no option for -d
$ date -h
date: illegal option -- h
Usage: date [-u] [+format]
date [-u] [mmddhhmm[[cc]yy]]
date [-a [-]sss.fff]
When I was unfortunate enough to have to slog through HP-UX idiosyncrasies, I'd often write my own little programs to do exactly what I wanted. Provided your hokey-pux machine is POSIXy, then you could do:
$ cat fromnow.c
#include <time.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
struct tm parsed;
strptime(argv[1], argv[2], &parsed);
printf("%.0f\n", difftime(time(NULL), mktime(&parsed)));
return 0;
}
Compile it:
$ cc -o fromnow fromnow.c
Run it:
$ ./fromnow 5/13/13 %D
119451988
which will then calculate the number of seconds between the current time and the date "5/13/13" formatted in American style "%D". The first argument is the date, the second argument is the format specifier for parsing that date. See man strptime for options.
This could be made more general, or less, depending upon how much you need to do date calculations.
Not sure which version of ksh you're using.
[STEP 101] $ echo ${.sh.version}
Version AJM 93u+ 2012-08-01
[STEP 102] $ date=12/15/16
[STEP 103] $ [[ $date =~ (..)/(..)/(..) ]]
[STEP 104] $ date=20${.sh.match[3]}-${.sh.match[1]}-${.sh.match[2]}
[STEP 105] $ echo $date
2016-12-15
[STEP 106] $ old_seconds=$( printf '%(%s)T' $date )
[STEP 107] $ echo $old_seconds
1481797007
[STEP 108] $ now_seconds=$( printf '%(%s)T' )
[STEP 109] $ echo $now_seconds
1487845024
[STEP 110] $ (( diff = now_seconds - old_seconds ))
[STEP 111] $ echo $diff
6048017
[STEP 112] $ echo $(( diff / 86400 )) days
70 days
[STEP 113] $
When you have awk, you can calculate the difference with
LASTRESETDATE="12/15/16"
endd="$(date '+%m/%d/%y')"
awk -v startdate="${LASTRESETDATE}" -v enddate="${endd}" 'BEGIN {
split(startdate,A,"[/]");
T1=mktime(A[3] " " A[1] " " A[2] " 12 0 0");
split(enddate,B,"[/]");
T2=mktime(B[3] " " B[1] " " B[2] " 12 0 0");
diffdays=(T2-T1)/(3600*24)
printf("%s\n",diffdays);
}'
When you need this often, and you do not have (the right version of) awk, you can make a lookup-table on another system.
With awk:
startd="12/15/16"
endd="$(date '+%m/%d/%y')"
awk -v startdate="${startd}" -v enddate="${endd}" 'BEGIN {
split(startdate,A,"[/]");
T1=mktime(A[3] " " A[1] " " A[2] " 12 0 0");
split(enddate,B,"[/]");
T2=mktime(B[3] " " B[1] " " B[2] " 12 0 0");
linenr=1;
while (T1 < T2) {
printf("%d %s\n",linenr++, strftime("%m/%d/%y",T1));
T1+=3600*24;
}
}'
Of course you can make a list with Excel or another tool.
EDIT: removed var that I only used prototyping the solution.

Change graph color in Labtalk using a Fortran program

I have a Fortran program that creates a bat file which holds the commands for Origin in Labtalk. In the end Origin will show a graph with several plots. At present all plotted lines are displayed in black. Now I want to change the color of the graphs. Can anyone tell me where and which command I have to change? I think this must happen somewhere here:
...
ch1=ADJUSTL(ch1)
WRITE(ch2,'(I3)')JJY(i)
ch2=ADJUSTL(ch2)
WRITE(4,489)CHAR(77+i),'aX'//ch1(1:LNBLNK(ch1))//'Y'
1//ch2(1:LNBLNK(ch2))
enddo
489 FORMAT(1H ,'%',A1,'=',A8,';')
ENDIF
FLINE='count='//NCHAR(1:LNBLNK(NCHAR))//';'
WRITE(4,'(1A260)')FLINE
WRITE(4,490)
490 FORMAT(1H ,'window -n Data;',
1/1H ,'open -w %A;',
1/1H ,'window -r %H Data ;',
1/1H ,'worksheet -t 1 4; worksheet -t 2 1;',
1/1H ,'worksheet -n 1 %M;')
do i=1,NUMB
WRITE(4,491)i+1,CHAR(77+i)
enddo
491 FORMAT(1H ,'worksheet -n ',I1,' %',A1,';')
WRITE(4,492)
492 FORMAT(1H ,'window -i ;',
1/1H ,'window -n plot Plot ;')
do i=1,NUMB
WRITE(4,493)CHAR(77+i),i+1
enddo
493 FORMAT(' %S=Data_%',A1,';',
1/1H ,'set %S -x Data_%M; set %S -c ',I1,';',
1/1H ,'layer -i %S',
1/1H ,'set %S -w 1000;' )
WRITE(4,494)
494 FORMAT(1H ,'axis -ps x g 3;axis -ps x a 3;axis -ps x l 1;',
1/1H ,'axis -ps y g 3;axis -ps y a 3;axis -ps y l 1;',
1/1H ,'layer.x.grid.majorwidth=0.3;layer.x.grid.minorwidth=0.1;',
1/1H ,'layer.x.grid.majorcolor=color(black);layer.x.grid.minor
1color=color(black);',
1/1H ,'page -o l;',
1/1H ,'rescale;')
CLOSE(UNIT=4)
...
From the Labtalk documentation of set:
Syntax: set name -c value
Set the plot line color and symbol edge color following the color palette [...]
set %c -c 2; // set color to be red
set %c -c 102; // set the next column on the right to be color index
set %c -c 524390; // set the next column on the left to be color index
This is actually done in your code:
do i=1,NUMB
WRITE(4,493)CHAR(77+i),i+1
enddo
493 FORMAT(' %S=Data_%',A1,';',
1/1H ,'set %S -x Data_%M; set %S -c ',I1,';',
1/1H ,'layer -i %S',
1/1H ,'set %S -w 1000;' )
Not that you chose I1 to denote the color value. Maybe your number is >9 at some point? Could you check the resulting file?
[Note that my answer is from the documentation only. I have no clue about Labtalk whatsoever. ]

How to gather characters usage statistics in text file using Unix commands?

I have got a text file created using OCR software - about one megabyte in size.
Some uncommon characters appears all over document and most of them are OCR errors.
I would like find all characters used in document to easily spot errors (like UNIQ command but for characters, not for lines).
I am on Ubuntu.
What Unix command I should use to display all characters used in text file?
This should do what you're looking for:
cat inputfile | sed 's/\(.\)/\1\n/g' | sort | uniq -c
The premise is that the sed puts each character in the file onto a line by itself, then the usual sort | uniq -c sequence strips out all but one of each unique character that occurs, and provides counts of how many times each occurred.
Also, you could append | sort -n to the end of the whole sequence to sort the output by how many times each character occurred. Example:
$ echo hello | sed 's/\(.\)/\1\n/g' | sort | uniq -c | sort -n
1
1 e
1 h
1 o
2 l
This will do it:
#!/usr/bin/perl -n
#
# charcounts - show how many times each code point is used
# Tom Christiansen <tchrist#perl.com>
use open ":utf8";
++$seen{ ord() } for split //;
END {
for my $cp (sort {$seen{$b} <=> $seen{$a}} keys %seen) {
printf "%04X %d\n", $cp, $seen{$cp};
}
}
Run on itself, that program produces:
$ charcounts /tmp/charcounts | head
0020 46
0065 20
0073 18
006E 15
000A 14
006F 12
0072 11
0074 10
0063 9
0070 9
If you want the literal character and/or name of the character, too, that’s easy to add.
If you want something more sophisticated, this program figures out characters by Unicode property. It may be enough for your purposes, and if not, you should be able to adapt it.
#!/usr/bin/perl
#
# unicats - show character distribution by Unicode character property
# Tom Christiansen <tchrist#perl.com>
use strict;
use warnings qw<FATAL all>;
use open ":utf8";
my %cats;
our %Prop_Table;
build_prop_table();
if (#ARGV == 0 && -t STDIN) {
warn <<"END_WARNING";
$0: reading UTF-8 character data directly from your tty
\tSo please type stuff...
\t and then hit your tty's EOF sequence when done.
END_WARNING
}
while (<>) {
for (split(//)) {
$cats{Total}++;
if (/\p{ASCII}/) { $cats{ASCII}++ }
else { $cats{Unicode}++ }
my $gcat = get_general_category($_);
$cats{$gcat}++;
my $subcat = get_general_subcategory($_);
$cats{$subcat}++;
}
}
my $width = length $cats{Total};
my $mask = "%*d %s\n";
for my $cat(qw< Total ASCII Unicode >) {
printf $mask, $width => $cats{$cat} || 0, $cat;
}
print "\n";
my #catnames = qw[
L Lu Ll Lt Lm Lo
N Nd Nl No
S Sm Sc Sk So
P Pc Pd Ps Pe Pi Pf Po
M Mn Mc Me
Z Zs Zl Zp
C Cc Cf Cs Co Cn
];
#for my $cat (sort keys %cats) {
for my $cat (#catnames) {
next if length($cat) > 2;
next unless $cats{$cat};
my $prop = length($cat) == 1
? ( " " . q<\p> . $cat )
: ( q<\p> . "{$cat}" . "\t" )
;
my $desc = sprintf("%-6s %s", $prop, $Prop_Table{$cat});
printf $mask, $width => $cats{$cat}, $desc;
}
exit;
sub get_general_category {
my $_ = shift();
return "L" if /\pL/;
return "S" if /\pS/;
return "P" if /\pP/;
return "N" if /\pN/;
return "C" if /\pC/;
return "M" if /\pM/;
return "Z" if /\pZ/;
die "not reached one: $_";
}
sub get_general_subcategory {
my $_ = shift();
return "Lu" if /\p{Lu}/;
return "Ll" if /\p{Ll}/;
return "Lt" if /\p{Lt}/;
return "Lm" if /\p{Lm}/;
return "Lo" if /\p{Lo}/;
return "Mn" if /\p{Mn}/;
return "Mc" if /\p{Mc}/;
return "Me" if /\p{Me}/;
return "Nd" if /\p{Nd}/;
return "Nl" if /\p{Nl}/;
return "No" if /\p{No}/;
return "Pc" if /\p{Pc}/;
return "Pd" if /\p{Pd}/;
return "Ps" if /\p{Ps}/;
return "Pe" if /\p{Pe}/;
return "Pi" if /\p{Pi}/;
return "Pf" if /\p{Pf}/;
return "Po" if /\p{Po}/;
return "Sm" if /\p{Sm}/;
return "Sc" if /\p{Sc}/;
return "Sk" if /\p{Sk}/;
return "So" if /\p{So}/;
return "Zs" if /\p{Zs}/;
return "Zl" if /\p{Zl}/;
return "Zp" if /\p{Zp}/;
return "Cc" if /\p{Cc}/;
return "Cf" if /\p{Cf}/;
return "Cs" if /\p{Cs}/;
return "Co" if /\p{Co}/;
return "Cn" if /\p{Cn}/;
die "not reached two: <$_> " . sprintf("U+%vX", $_);
}
sub build_prop_table {
for my $line (<<"End_of_Property_List" =~ m{ \S .* \S }gx) {
L Letter
Lu Uppercase_Letter
Ll Lowercase_Letter
Lt Titlecase_Letter
Lm Modifier_Letter
Lo Other_Letter
M Mark (combining characters, including diacritics)
Mn Nonspacing_Mark
Mc Spacing_Mark
Me Enclosing_Mark
N Number
Nd Decimal_Number (also Digit)
Nl Letter_Number
No Other_Number
P Punctuation
Pc Connector_Punctuation
Pd Dash_Punctuation
Ps Open_Punctuation
Pe Close_Punctuation
Pi Initial_Punctuation (may behave like Ps or Pe depending on usage)
Pf Final_Punctuation (may behave like Ps or Pe depending on usage)
Po Other_Punctuation
S Symbol
Sm Math_Symbol
Sc Currency_Symbol
Sk Modifier_Symbol
So Other_Symbol
Z Separator
Zs Space_Separator
Zl Line_Separator
Zp Paragraph_Separator
C Other (means not L/N/P/S/Z)
Cc Control (also Cntrl)
Cf Format
Cs Surrogate (not usable)
Co Private_Use
Cn Unassigned
End_of_Property_List
my($short_prop, $long_prop) = $line =~ m{
\b
( \p{Lu} \p{Ll} ? )
\s +
( \p{Lu} [\p{L&}_] + )
\b
}x;
$Prop_Table{$short_prop} = $long_prop;
}
}
For example:
$ unicats book.txt
2357232 Total
2357199 ASCII
33 Unicode
1604949 \pL Letter
74455 \p{Lu} Uppercase_Letter
1530485 \p{Ll} Lowercase_Letter
9 \p{Lo} Other_Letter
10676 \pN Number
10676 \p{Nd} Decimal_Number
19679 \pS Symbol
10705 \p{Sm} Math_Symbol
8365 \p{Sc} Currency_Symbol
603 \p{Sk} Modifier_Symbol
6 \p{So} Other_Symbol
111899 \pP Punctuation
2996 \p{Pc} Connector_Punctuation
6145 \p{Pd} Dash_Punctuation
11392 \p{Ps} Open_Punctuation
11371 \p{Pe} Close_Punctuation
79995 \p{Po} Other_Punctuation
548529 \pZ Separator
548529 \p{Zs} Space_Separator
61500 \pC Other
61500 \p{Cc} Control
As far as using *nix commands, the answer above is good, but it doesn't get usage stats.
However, if you actually want stats (like the rarest used, median, most used, etc) on the file, this Python should do it.
def get_char_counts(fname):
f = open(fname)
usage = {}
for c in f.read():
if c not in usage:
usage.update({c:1})
else:
usage[c] += 1
return usage

Get specific lines from a text file

I am working on a UNIX box, and trying to run an application, which gives some debug logs to the standard output. I have redirected this output to a log file, but now wish to get the lines where the error is being shown.
My problem here is that a simple
cat output.log | grep FAIL
does not help out. As this shows only the lines which have FAIL in them. I want some more information along with this. Like the 2-3 lines above this line with FAIL. Is there any way to do this via a simple shell command? I would like to have a single command line (can have pipes) to do the above.
grep -C 3 FAIL output.log
Note that this also gets rid of the useless use of cat (UUOC).
grep -A $NUM
This will print $NUM lines of trailing context after matches.
-B $NUM prints leading context.
man grep is your best friend.
So in your case:
cat log | grep -A 3 -B 3 FAIL
I have two implementations of what I call sgrep, one in Perl, one using just pre-Perl (pre-GNU) standard Unix commands. If you've got GNU grep, you've no particular need of these. It would be more complex to deal with forwards and backwards context searches, but that might be a useful exercise.
Perl solution:
#!/usr/perl/v5.8.8/bin/perl -w
#
# #(#)$Id: sgrep.pl,v 1.6 2007/09/18 22:55:20 jleffler Exp $
#
# Perl-based SGREP (special grep) command
#
# Print lines around the line that matches (by default, 3 before and 3 after).
# By default, include file names if more than one file to search.
#
# Options:
# -b n1 Print n1 lines before match
# -f n2 Print n2 lines following match
# -n Print line numbers
# -h Do not print file names
# -H Do print file names
use strict;
use constant debug => 0;
use Getopt::Std;
my(%opts);
sub usage
{
print STDERR "Usage: $0 [-hnH] [-b n1] [-f n2] pattern [file ...]\n";
exit 1;
}
usage unless getopts('hnf:b:H', \%opts);
usage unless #ARGV >= 1;
if ($opts{h} && $opts{H})
{
print STDERR "$0: mutually exclusive options -h and -H specified\n";
exit 1;
}
my $op = shift;
print "# regex = $op\n" if debug;
# print file names if -h omitted and more than one argument
$opts{F} = (defined $opts{H} || (!defined $opts{h} and scalar #ARGV > 1)) ? 1 : 0;
$opts{n} = 0 unless defined $opts{n};
my $before = (defined $opts{b}) ? $opts{b} + 0 : 3;
my $after = (defined $opts{f}) ? $opts{f} + 0 : 3;
print "# before = $before; after = $after\n" if debug;
my #lines = (); # Accumulated lines
my $tail = 0; # Line number of last line in list
my $tbp_1 = 0; # First line to be printed
my $tbp_2 = 0; # Last line to be printed
# Print lines from #lines in the range $tbp_1 .. $tbp_2,
# leaving $leave lines in the array for future use.
sub print_leaving
{
my ($leave) = #_;
while (scalar(#lines) > $leave)
{
my $line = shift #lines;
my $curr = $tail - scalar(#lines);
if ($tbp_1 <= $curr && $curr <= $tbp_2)
{
print "$ARGV:" if $opts{F};
print "$curr:" if $opts{n};
print $line;
}
}
}
# General logic:
# Accumulate each line at end of #lines.
# ** If current line matches, record range that needs printing
# ** When the line array contains enough lines, pop line off front and,
# if it needs printing, print it.
# At end of file, empty line array, printing requisite accumulated lines.
while (<>)
{
# Add this line to the accumulated lines
push #lines, $_;
$tail = $.;
printf "# array: N = %d, last = $tail: %s", scalar(#lines), $_ if debug > 1;
if (m/$op/o)
{
# This line matches - set range to be printed
my $lo = $. - $before;
$tbp_1 = $lo if ($lo > $tbp_2);
$tbp_2 = $. + $after;
print "# $. MATCH: print range $tbp_1 .. $tbp_2\n" if debug;
}
# Print out any accumulated lines that need printing
# Leave $before lines in array.
print_leaving($before);
}
continue
{
if (eof)
{
# Print out any accumulated lines that need printing
print_leaving(0);
# Reset for next file
close ARGV;
$tbp_1 = 0;
$tbp_2 = 0;
$tail = 0;
#lines = ();
}
}
Pre-Perl Unix solution (using plain ed, sed, and sort - though it uses getopt which was not necessarily available back then):
#!/bin/ksh
#
# #(#)$Id: old.sgrep.sh,v 1.5 2007/09/15 22:15:43 jleffler Exp $
#
# Special grep
# Finds a pattern and prints lines either side of the pattern
# Line numbers are always produced by ed (substitute for grep),
# which allows us to eliminate duplicate lines cleanly. If the
# user did not ask for numbers, these are then stripped out.
#
# BUG: if the pattern occurs in in the first line or two and
# the number of lines to go back is larger than the line number,
# it fails dismally.
set -- `getopt "f:b:hn" "$#"`
case $# in
0) echo "Usage: $0 [-hn] [-f x] [-b y] pattern [files]" >&2
exit 1;;
esac
# Tab required - at least with sed (perl would be different)
# But then the whole problem would be different if implemented in Perl.
number="'s/^\\([0-9][0-9]*\\) /\\1:/'"
filename="'s%^%%'" # No-op for sed
f=3
b=3
nflag=no
hflag=no
while [ $# -gt 0 ]
do
case $1 in
-f) f=$2; shift 2;;
-b) b=$2; shift 2;;
-n) nflag=yes; shift;;
-h) hflag=yes; shift;;
--) shift; break;;
*) echo "Unknown option $1" >&2
exit 1;;
esac
done
pattern="${1:?'No pattern'}"
shift
case $# in
0) tmp=${TMPDIR:-/tmp}/`basename $0`.$$
trap "rm -f $tmp ; exit 1" 0
cat - >$tmp
set -- $tmp
sort="sort -t: -u +0n -1"
;;
*) filename="'s%^%'\$file:%"
sort="sort -t: -u +1n -2"
;;
esac
case $nflag in
yes) num_remove='s/[0-9][0-9]*://';;
no) num_remove='s/^//';;
esac
case $hflag in
yes) fileremove='s%^$file:%%';;
no) fileremove='s/^//';;
esac
for file in $*
do
echo "g/$pattern/.-${b},.+${f}n" |
ed - $file |
eval sed -e "$number" -e "$filename" |
$sort |
eval sed -e "$fileremove" -e "$num_remove"
done
rm -f $tmp
trap 0
exit 0
The shell version of sgrep was written in February 1989, and bug fixed in May 1989. It then remained unchanged except for an administrative change (SCCS to RCS transition) in 1997 until 2007, when I added the -h option. I switched to the Perl version in 2007.
http://thedailywtf.com/Articles/The_Complicator_0x27_s_Gloves.aspx
You can use sed to print specific lines, lets say you want line 20
sed '20 p' -n FILE_YOU_WANT_THE_LINE_FROM
Done.
-n prevents echoing lines from the file. The part in quotes is a sed rule to apply, it specifies that you want the rule to apply to line 20, and you want to print.
With GNU grep on Windows:
$ grep --context 3 FAIL output.log
$ grep --help | grep context
-B, --before-context=NUM print NUM lines of leading context
-A, --after-context=NUM print NUM lines of trailing context
-C, --context=NUM print NUM lines of output context
-NUM same as --context=NUM

Resources