Find text value in file, print it out? - shell

I've got a shell script. I want to open a file, and copy out a bit of text from the file. Example:
// foo.java
public static int ID_RED = 100;
public static int ID_GREEN = 200;
public static int ID_BLUE = 300;
// pseudo-code:
int pos = find("public static int ID_RED = ");
echo(file.substring(pos, end of line);
pos = find("public static int ID_GREEN = ");
echo(file.substring(pos, end of line);
pos = find("public static int ID_BLUE = ");
echo(file.substring(pos, end of line);
// desired output:
100
200
300
So I want to open foo.java, and print out the values found at the end of those lines. I think it'd be easier to do this in perl or python, wanted to see if there was a simple way to do this in a shell script, though,

sed -n '/^public static int ID_/{s/;.*$//;s/.* //p;}' foo.java
This doesn't exactly match the criteria implied by your pseudo-code, but it does the job for this particular input. Consider replacing ^public by ^ *public if there might be leading whitespace. Replace ID_ by ID_\(RED\|GREEN\|BLUE\)\> if you don't want to match ID_YELLOW, for example.

This should help you get started:
#!/bin/bash
grep "public static int ID_RED = " foo.java | cut -d " " -f 6 | tr -d \;
grep "public static int ID_GREEN = " foo.java | cut -d " " -f 6 | tr -d \;
grep "public static int ID_BLUE = " foo.java | cut -d " " -f 6 | tr -d \;

awk -F '[ ;]' '/public static int/ {print $(NF-1)}' foo.java
or
sed -n '/public static int/ {s/^.*= *\([0-9]\+\);/\1/; p}' foo.java

Related

find and replace line with variable use sed

sn=$(./libs/ideviceinfo | grep ^SerialNumber | awk {'printf $NF'})
type=$(./libs/ideviceinfo | grep ProductType | awk {'printf $NF'})
udid=$(./libs/ideviceinfo | grep UniqueDeviceID | awk {'printf $NF'})
I want to replace variable value into this txt file
{
"InternationalMobileEquipmentIdentity" = "355331088790894";
"SerialNumber" = "C6KSJM0AHG6W";
"InternationalMobileSubscriberIdentity" = "";
"ProductType" = "iPhone9,1";
"UniqueDeviceID" = "69bae2fcc0da3e6e3373f583ef856e02c88026eb";
"ActivationRandomness" = "25E7742B-76A7-4C31-9F49-52D17A817B2F";
"ActivityURL" = "https://albert.apple.com/deviceservices/activity";
"IntegratedCircuitCardIdentity" = "";
"CertificateURL" = "https://albert.apple.com/deviceservices/certifyMe";
"PhoneNumberNotificationURL" = "https://albert.apple.com/deviceservices/phoneHome";
"ActivationTicket" = "";
}
i try using sed:
sed 's/"SerialNumber.*/"SerialNumber" = "$sn";/g' ./file/bp.txt > ./file/bp1.txt
The output is not as expected: "SerialNumber" = "$sn";
Hope you guys can help me
p/s: can you help me if 1 command can replace 3 variable values ​​at the same time, that would be great
The problem here is one of shell quoting. Using single quotes means that everything inside will not go through substitution.
The following should fix your problem:
sed 's/"SerialNumber.*/"SerialNumber" = "'"$sn"'";/g' ./file/bp.txt > ./file/bp1.txt

Sed substitution - spaces by tabs

I'm trying to formatting a batch of .c files via the sed command in a shell script to align properly the functions name. I'm replacing int(space)function1() by int(3tab)function1()
int function1(int foo)
{
*my_function_code*
}
char function2(int foo)
{
*my_function_code*
}
int main(int foo)
{
*my_function_code*
}
I'm actually using the following loop to apply my substitution :
#align global scope
printf " Correct global scope alignement...\n"
for file in ${FILES[#]}; do
sed -i -e 's/^int */int /g' \
-i -e 's/^char */char /g' \
-i -e 's/^float */float /g' \
-i -e 's/^long int */long int /g' ${file}
done
The problem is, if I rerun the script, instead of doing nothing, it will add multiple tabs again. Giving me this :
int function1(int foo)
{
*my_function_code*
}
char function2(int foo)
{
*my_function_code*
}
int main(int foo)
{
*my_function_code*
}
The * isn't supposed to looking only for spaces and not tabulations or is it considered as all blanks characters ?
Could you please try following, written and tested with shown samples. Simply checking if line starts either from int or char(you could add float and long int too in condition) then substitute spaces in 3 tabs here.
sed -E '/^int|^char/s/ +/\t\t\t/' Input_file

Using awk in a wrong approach

I am told I used awk in a wrong approach in the below code, but I am dumbfounded as to how to improve my code so that it is more simpler to read.
read -r bookName
read -r authorName
if grep -iqx "$bookName:$authorName" cutText.txt
then
lineNum=`awk -v bookName="$bookName" -v authorName="$authorName" '$0 ~ bookName ":" authorName {print NR} BEGIN{IGNORECASE=1}' BookDB.txt`
echo "Enter a new title"
read -r newTitle
awk -F":" -v bookName="$bookName" -v newTitle="$newTitle" -v lineNum="$lineNum" 'NR==lineNum{gsub(bookName, newTitle)}1' cutText.txt > temp2.txt
mv -f temp2.txt cutText.txt
else
echo "Error"
fi
My cutText.txt contains content as shown below:
Hairy Potter:Rihanna
MARY IS A LITTLE LAMB:Kenny
Sing along:May
This program basically update a new title in cutText.txt. If a user wants to change MARY IS A LITTLE LAMB to Mary is not a lamb, he will enter the new title and cutText.txt will replace the original title with Mary is not a lamb.
A problem arises now that if a user enter "Mary is a little lamb" for $newTitle, this code of works just doesn't work, because it does take the case into account.
It will only work is user types "MARY IS A LITTLE LAMB". I came to be aware that BEGIN{IGNORECASE=1} is gawk-sepcific, therefore it cannot be used in awk.
How can I script this better so I can ignore case in user input? Thank you!
This uses exact string matching and so cannot fail on partial matches or if your old title contains : or regexp metacharacters or if the new title contains backreferences (e.g. &) or if a backslash (\) appears in any field or any of the other situations that your other scripts to date will fail on:
$ cat tst.sh
read -r oldTitle
read -r authorName
echo "Enter a new title"
read -r newTitle
awk '
BEGIN {
ot=ARGV[1]; nt=ARGV[2]; an=ARGV[3]
ARGV[1] = ARGV[2] = ARGV[3] = ""
}
tolower($0) == tolower(ot":"an) {
$0 = nt":"an
found = 1
}
{ print }
END {
if ( !found ) {
print "Error" | "cat>&2"
}
}
' "$oldTitle" "$newTitle" "$authorName" cutText.txt > temp2.txt &&
mv -f temp2.txt cutText.txt
.
$ cat cutText.txt
Hairy Potter:Rihanna
MARY IS A LITTLE LAMB:Kenny
Sing along:May
$ ./tst.sh
mary is a little lamb
kenny
Enter a new title
Mary is not a lamb
$ cat cutText.txt
Hairy Potter:Rihanna
Mary is not a lamb:kenny
Sing along:May
I'm populating the awk variables from ARGV[] because if I populated them using -v var=val or var=val in the arg list then any backslashes would be interpreted and so \t, for example, would become a literal tab character. See the shell FAQ article I wrote about that a long time ago - http://cfajohnson.com/shell/cus-faq-2.html#Q24.
I changed bookName to oldTitle, btw just because that seems to make more sense in relation to newTitle. No functional difference.
When doing any text manipulation it's extremely important to understand the differences between strings and the various regexp flavors (BREs/EREs/PCREs) and between partial and full matches.
grep operates on BREs by default, on EREs given the -E arg, on PCREs given the -P arg, and on strings given the -F arg.
sed operates on BREs by default, on EREs given the -E arg. sed does not support PCREs. sed also cannot operate on strings and to make your regexps behave as if they were strings is painful, see is-it-possible-to-escape-regex-metacharacters-reliably-with-sed.
awk operates on both EREs and strings by default. You just use EREs with regexp operators and strings with string operators (see https://www.gnu.org/software/gawk/manual/gawk.html#String-Functions).
So if, as in your case, you need all characters in your text treated literally then that is a string, not a regexp, so you should not be using sed on it, and if you want to quickly find a string in a file and are happy with a partial match, you should use grep, but if you want to do anything beyond that such as change a string in a file or do an exact match then you should use awk.
To get you started. Create files
r.awk
function asplit(str, arr, sep, temp, i, n) { # make an assoc array from str
n = split(str, temp, sep)
for (i = 1; i <= n; i++)
arr[temp[i]]++
return n
}
function regexpify(s, back, quote, rest, all, meta, n, c, u, l, ans) {
back = "\\"; quote = "\"";
rest = "^$.[]|()*+?"
all = back quote rest
asplit(all, meta, "")
n = length(s)
for (i=1; i<=n; i++) {
c = substr(s, i, 1)
if (c in meta)
ans = ans back c
else if ((u = toupper(c)) != (l = tolower(c)))
ans = ans "[" l u "]"
else
ans = ans c
}
return ans
}
BEGIN {
old = regexpify(old)
sep = ":"; m = length(sep)
}
NR == n {
i = index($0, sep)
fst = substr($0, 1, i-m)
scn = substr($0, i+m )
gsub(old, new, fst)
print fst sep scn
next
}
{
print
}
cutText.txt
Hairy Potter:Rihanna
MARY IS A LITTLE LAMB:Kenny
Sing along:May
Usage:
awk -v n=2 -v old="MArY iS A LIttLE lAmb" -v new="Mary is not a lamb" -f r.awk cutText.txt
Expected output:
Hairy Potter:Rihanna
Mary is not a lamb:Kenny
Sing along:May
OK GUYS I JUST REALISED I AM DUMB AS ****
I was tearing my hair out for the whole day and all I had to do was to do this.
lineNum=`grep -in "$bookName:$authorName" BookDB.txt | cut -f1 -d":"`
sed -i "${lineNum}s/$bookName/$newTitle/I" BookDB.txt cutText.txt
Omg I feel like killing myself.

Show different context on different grep keyword?

I know -A -B -C could be used to show context around the grep keyword.
My question is, how to show different context on different keyword?
For example, how do I show -A 5 for cat, -B 4 for dog, and -C 1 for monkey:
egrep -A3 "cat|dog|monkey" <file>
// this just show 3 after lines for each keyword.
i don't think there's any way to do it with a single grep call, but you could run it through grep once for each variable and concatenate the output:
var=$(grep -n -A 5 cat file)$'\n'$(grep -n -B 4 dog file)$'\n'$(grep -n -C 1 monkey file)
var=$(sort -un <(echo "$var"))
now echo "$var" will produce the same output as you would have gotten from your single command, plus line numbers and context indicators (the : prefix indicates a line that matched the pattern exactly, and the - prefix indicates a line being included because of the -A -B and/or -C options).
the reason i included the line numbers thus far is to preserve the order of the results you would have seen had you managed to do this in one statement. if you like them, great, but if not, you can use the following line to cut them out:
var=$(cut -d: -f2- <(echo "$var") | cut -d- -f2-)
this passes it through once to cut the exact matching lines' prefixes, then again to cut the context matches' prefixes.
pretty? no. but it works.
I'm afraid grep won't do that. You'll have to use a different tool. Perhaps write your own program.
Something like this would do it:
awk '
BEGIN{ ARGV[ARGC++] = ARGV[1] }
function prtB(nr) { for (i=FNR-nr; i<FNR; i++) print a[i] }
function prtA(nr) { for (i=FNR+1; i<=FNR+nr; i++) print a[i] }
NR==FNR{ a[NR]; next }
/cat/ { print; prtA(5) }
/dog/ { prtB(4); print }
/monkey/ { prtB(1); print; prtA(1) }
' file
check the math on the loops in the functions. You didn't say how you'd want to handle lines that contain monkey AND dog, for example.
EDIT: here's an untested solution that would print the maximum context around any match and let you specify the contexts on the command line and won't use as much memory as the above cheap and cheerful solution:
awk -v cxts="cat:0:5\ndog:4:0\nmonkey:1:1" '
BEGIN{
ARGV[ARGC++] = ARGV[1]
numCxts = split(cxts,cxtsA,RS)
for (i=1;i<=numCxts;i++) {
regex = cxtsA[i]
n = split(regex,rangeA,/:/)
sub(/:[^:]+:[^:]+$/,"",regex)
endA[regex] = rangeA[n]
startA[regex] = rangeA[n-1]
regexA[regex]
}
}
NR==FNR{
for (regex in regexA) {
if ($0 ~ regex) {
start = NR - startA[regex]
end = NR + endA[regex]
for (i=start; i<=end; i++) {
prt[i]
}
}
}
next
}
FNR in prt
' file
Separate the searched for patterns in the cxts variable with whatever your RS value is, newline by default.

sed: how to replace CR and/or LF with "\r" "\n", so any file will be in one line

I have files like
aaa
bbb
ccc
I need them to sed into aaa\r\nbbb\r\nccc
It should work either for unix and windows replacing them with \r or \r\n accordingly
The problem is that sed adds \n at the end of line but keeps lines separated. How can I fix it?
These two commands together should do what you want:
sed ':a;N;$!ba;s/\r/\\r/g'
sed ':a;N;$!ba;s/\n/\\n/g'
Pass your input file through both to get the output you want. Theres probably a way to combine them into a single expression.
Stolen and Modified from this question:
How can I replace a newline (\n) using sed?
It's possible to merge lines in sed, but personally, I consider needing to change line breaks a sign that it's time to give up on sed and use a more powerful language instead. What you want is one line of perl:
perl -e 'undef $/; while (<>) { s/\n/\\n/g; s/\r/\\r/g; print $_, "\n" }'
or 12 lines of python:
#! /usr/bin/python
import fileinput
from sys import stdout
first = True
for line in fileinput.input(mode="rb"):
if fileinput.isfirstline() and not first:
stdout.write("\n")
if line.endswith("\r\n"): stdout.write(line[:-2] + "\\r\\n")
elif line.endswith("\n"): stdout.write(line[:-1] + "\\n")
elif line.endswith("\r"): stdout.write(line[:-1] + "\\r")
first = False
if not first: stdout.write("\n")
or 10 lines of C to do the job, but then a whole bunch more because you have to process argv yourself:
#include <stdio.h>
void process_one(FILE *fp)
{
int c;
while ((c = getc(fp)) != EOF)
if (c == '\n') fputs("\\n", stdout);
else if (c == '\r') fputs("\\r", stdout);
else putchar(c);
fclose(fp);
putchar('\n');
}
int main(int argc, char **argv)
{
FILE *cur;
int i, consumed_stdin = 0, rv = 0;
if (argc == 1) /* no arguments */
{
process_one(stdin);
return 0;
}
for (i = 1; i < argc; i++)
{
if (argc[i][0] == '-' && argc[i][1] == 0)
{
if (consumed_stdin)
{
fputs("cannot read stdin twice\n", stderr);
rv = 1;
continue;
}
cur = stdin;
consumed_stdin = 1;
}
else
{
cur = fopen(ac[i], "rb");
if (!cur)
{
perror(ac[i]);
rv = 1;
continue;
}
}
process_one(cur);
}
return rv;
}
awk '{printf("%s\\r\\n",$0)} END {print ""}' file
tr -s '\r' '\n' <file | unix2dos
EDIT (it's been pointed out that the above misses the point entirely! •///•)
tr -s '\r' '\n' <file | perl -pe 's/\s+$/\\r\\n/'
The tr gets rid of empty lines and dos line endings. The pipe means two processes—good on modern hardware.

Resources