Working with AWK regex - regex

I have a file in which have values in following format-
20/01/2012 01:14:27;UP;UserID;User=bob email=abc#sample.com
I want to pick each value from this file (not labels). By saying label, i mean to say that for string email=abc#sample.com, i only want to pick abc#sample.com and for sting User=bob, i only want to pic bob. All the Space separated values are easy to pick but i am unable to pick the values separated by Semi colon. Below is the command i am using in awk-
awk '{print "1=",$1} /;/{print "2=",$2,"3=",$3}' sample_file
In $2, i am getting the complete string till bob and rest of the string is assigned to $3. Although i can work with substr provided with awk but i want to be on safe side, string length may vary.
Can somebody tell me how to design such regex to parse my file.

You can set multiple delimiters using awk -F:
awk -F "[ \t;=]+" '{ print $1, $2, $3, $4, $5, $6, $7, $8 }' file.txt
Results:
value1 value2 value3 value4 label1 value5 label2 value6
EDIT:
You can remove anything before the equal signs using sub (/[^=]*=/,"", $i). This will allow you to just print the 'values':
awk 'BEGIN { FS="[ \t;]+"; OFS=" " } { for (i=1; i<=NF; i++) { sub (/[^=]*=/,"", $i); line = (line ? line OFS : "") $i } print line; line = "" }' file.txt
Results:
20/01/2012 01:14:27 UP UserID bob abc#sample.com

Related

awk sub with a capturing group into the replacement

I am writing an awk oneliner for this purpose:
file1:
1 apple
2 orange
4 pear
file2:
1/4/2/1
desired output: apple/pear/orange/apple
addendum: Missing numbers should be best kept unchanged 1/4/2/3 = apple/pear/orange/3 to prevent loss of info.
Methodology:
Build an associative array key[$1] = $2 for file1
capture all characters between the slashes and replace them by matching to the key of associative array eg key[4] = pear
Tried:
gawk 'NR==FNR { key[$1] = $2 }; NR>FNR { r = gensub(/(\w+)/, "key[\\1]" , "g"); print r}' file1.txt file2.txt
#gawk because need to use \w+ regex
#gensub used because need to use a capturing group
Unfortunately, results are
1/4/2/1
key[1]/key[4]/key[2]/key[1]
Any suggestions? Thank you.
You may use this awk:
awk -v OFS='/' 'NR==FNR {key[$1] = $2; next}
{for (i=1; i<=NF; ++i) if ($i in key) $i = key[$i]} 1' file1 FS='/' file2
apple/pear/orange/apple
Note that if numbers from file2 don't exist in key array then it will make those fields empty.
file1 FS='/' file2 will keep default field separators for file1 but will use / as field separator while reading file2.
EDIT: In case you don't have a match in file2 from file and you want to keep original value as it is then try following:
awk '
FNR==NR{
arr[$1]=$2
next
}
{
val=""
for(i=1;i<=NF;i++){
val=(val=="" ? "" : val FS) (($i in arr)?arr[$i]:$i)
}
print val
}
' file1 FS="/" file2
With your shown samples please try following.
awk '
FNR==NR{
arr[$1]=$2
next
}
{
val=""
for(i=1;i<=NF;i++){
val = (val=="" ? "" : val FS) arr[$i]
}
print val
}
' file1 FS="/" file2
Explanation: Reading Input_file1 first and creating array arr with index of 1st field and value of 2nd field then setting field separator as / and traversing through each field os file2 and saving its value in val; printing it at last for each line.
Like #Sundeep comments in the comments, you can't use backreference as an array index. You could mix match and gensub (well, I'm using sub below). Not that this would be anywhere suggested method but just as an example:
$ awk '
NR==FNR {
k[$1]=$2 # hash them
next
}
{
while(match($0,/[0-9]+/)) # keep doing it while it lasts
sub(/[0-9]+/,k[substr($0,RSTART,RLENGTH)]) # replace here
}1' file1 file2
Output:
apple/pear/orange/apple
And of course, if you have k[1]="word1", you'll end up with a neverending loop.
With perl (assuming key is always found):
$ perl -lane 'if(!$#ARGV){ $h{$F[0]}=$F[1] }
else{ s|[^/]+|$h{$&}|g; print }' f1 f2
apple/pear/orange/apple
if(!$#ARGV) to determine first file (assuming exactly two files passed)
$h{$F[0]}=$F[1] create hash based on first field as key and second field as value
[^/]+ match non / characters
$h{$&} get the value based on matched portion from the hash
If some keys aren't found, leave it as is:
$ cat f2
1/4/2/1/5
$ perl -lane 'if(!$#ARGV){ $h{$F[0]}=$F[1] }
else{ s|[^/]+|exists $h{$&} ? $h{$&} : $&|ge; print }' f1 f2
apple/pear/orange/apple/5
exists $h{$&} checks if the matched portion exists as key.
Another approach using awk without loop:
awk 'FNR==NR{
a[$1]=$2;
next
}
$1 in a{
printf("%s%s",FNR>1 ? RS: "",a[$1])
}
END{
print ""
}' f1 RS='/' f2
$ cat f1
1 apple
2 orange
4 pear
$ cat f2
1/4/2/1
$ awk 'FNR==NR{a[$1]=$2;next}$1 in a{printf("%s%s",FNR>1?RS:"",a[$1])}END{print ""}' f1 RS='/' f2
apple/pear/orange/apple

Removing multiple delimiters between outside delimiters on each line

Using awk or sed in a bash script, I need to remove comma separated delimiters that are located between an inner and outer delimiter. The problem is that wrong values ends up in the wrong columns, where only 3 columns are desired.
For example, I want to turn this:
2020/11/04,Test Account,569.00
2020/11/05,Test,Account,250.00
2020/11/05,More,Test,Accounts,225.00
Into this:
2020/11/04,Test Account,569.00
2020/11/05,Test Account,250.00
2020/11/05,More Test Accounts,225.00
I've tried to use a few things, testing regex:
But I cannot find a solution to only select the commas in order to remove.
awk -F, '{ printf "%s,",$1;for (i=2;i<=NF-2;i++) { printf "%s ",$i };printf "%s,%s\n",$(NF-1),$NF }' file
Using awk, print the first comma delimited field and then loop through the rest of the field up to the last but 2 field printing the field followed by a space. Then for the last 2 fields print the last but one field, a comma and then the last field.
With GNU awk for the 3rd arg to match():
$ awk -v OFS=, '{
match($0,/([^,]*),(.*),([^,]*)/,a)
gsub(/,/," ",a[2])
print a[1], a[2], a[3]
}' file
2020/11/04,Test Account,569.00
2020/11/05,Test Account,250.00
2020/11/05,More Test Accounts,225.00
or with any awk:
$ awk '
BEGIN { FS=OFS="," }
{
n = split($0,a)
gsub(/^[^,]*,|,[^,]*$/,"")
gsub(/,/," ")
print a[1], $0, a[n]
}
' file
2020/11/04,Test Account,569.00
2020/11/05,Test Account,250.00
2020/11/05,More Test Accounts,225.00
Use this Perl one-liner:
perl -F',' -lane 'print join ",", $F[0], "#F[1 .. ($#F-1)]", $F[-1];' in.csv
The Perl one-liner uses these command line flags:
-e : Tells Perl to look for code in-line, instead of in a file.
-n : Loop over the input one line at a time, assigning it to $_ by default.
-l : Strip the input line separator ("\n" on *NIX by default) before executing the code in-line, and append it when printing.
-a : Split $_ into array #F on whitespace or on the regex specified in -F option.
-F',' : Split into #F on comma, rather than on whitespace.
$F[0] : first element of the array #F (= first comma-delimited value).
$F[-1] : last element of #F.
#F[1 .. ($#F-1)] : elements of #F between the second from the start and the second from the end, inclusive.
"#F[1 .. ($#F-1)]" : the above elements, joined on blanks into a string.
join ",", ... : join the LIST "..." on a comma, and return the resulting string.
SEE ALSO:
perldoc perlrun: how to execute the Perl interpreter: command line switches
perl -pe 's{,\K.*(?=,)}{$& =~ y/,/ /r}e' file
sed -e ':a' -e 's/\(,[^,]*\),\([^,]*,\)/\1 \2/; t a' file
awk '{$1=$1","; $NF=","$NF; gsub(/ *, */,","); print}' FS=, file
awk '{for (i=2; i<=NF; ++i) $i=(i>2 && i<NF ? " " : ",") $i} 1' FS=, OFS= file
awk doesn't support look arounds, we could have it by using match function of awk; using that could you please try following, written and tested with shown samples in GNU awk.
awk '
match($0,/,.*,/){
val=substr($0,RSTART+1,RLENGTH-2)
gsub(/,/," ",val)
print substr($0,1,RSTART) val substr($0,RSTART+RLENGTH-1)
}
' Input_file
Yet another perl
$ perl -pe 's/(?:^[^,]*,|,[^,]*$)(*SKIP)(*F)|,/ /g' ip.txt
2020/11/04,Test Account,569.00
2020/11/05,Test Account,250.00
2020/11/05,More Test Accounts,225.00
(?:^[^,]*,|,[^,]*$) matches first/last field along with the comma character
(*SKIP)(*F) this would prevent modification of preceding regexp
|, provide , as alternate regexp to be matched for modification
With sed (assuming \n is supported by the implementation, otherwise, you'll have to find a character that cannot be present in the input)
sed -E 's/,/\n/; s/,([^,]*)$/\n\1/; y/,/ /; y/\n/,/'
s/,/\n/; s/,([^,]*)$/\n\1/ replace first and last comma with newline character
y/,/ / replace all comma with space
y/\n/,/ change newlines back to comma
A similar answer to Timur's, in awk
awk '
BEGIN { FS = OFS = "," }
function join(start, stop, sep, str, i) {
str = $start
for (i = start + 1; i <= stop; i++) {
str = str sep $i
}
return str
}
{ print $1, join(2, NF-1, " "), $NF }
' file.csv
It's a shame awk doesn't ship with a join function builtin

How to extract url paths recursively

i want to list all endpoints in a list of url like
https://test123.com/endpoint1/endpoint2/endpoint3
https://test456.com/endpoint1/endpoint2/endpoint3
https://test789.com/endpoint1/endpoint2/endpoint3
into output like
https://test123.com/
https://test123.com/endpoint1/
https://test123.com/endpoint1/endpoint2/
https://test123.com/endpoint1/endpoint2/endpoint3
https://test456.com/
https://test456.com/endpoint1/
https://test456.com/endpoint1/endpoint2/
https://test456.com/endpoint1/endpoint2/endpoint3
And so on, listing all endpoints recursively so i would do something with each endpoint.
I tried to use this but it print it separately.
awk '$1=$1' FS="/" OFS="\n"
thanks
Could you please try following, written and tested with shown samples.
awk '
match($0,/http[s]?:\/\/[^/]*\//){
first=substr($0,RSTART,RLENGTH)
val=substr($0,RSTART+RLENGTH)
num=split(val,array,"/")
print first
for(i=1;i<=num;i++){
value=(value?value "/":"")array[i]
print first value
}
val=first=value=""
}' Input_file
Explanation: Adding detailed explanation for above.
awk ' ##Starting awk program from here.
match($0,/http[s]?:\/\/[^/]*\//){ ##Using match function which matches http OR https :// then till first occurrence of /
first=substr($0,RSTART,RLENGTH) ##Creating first with sub-string which starts from RSTART till RLENGTH value of current line.
val=substr($0,RSTART+RLENGTH) ##Creating val which has rest of line out of match function in 3rd line of code.
num=split(val,array,"/") ##Splitting val into array with delimiter / here.
print first ##Printing first here.
for(i=1;i<=num;i++){ ##Starting for loop till value of num from i=1 here.
value=(value?value "/":"")array[i] ##Creating value which has array[i] and keep adding in its previous value to it.
print first value ##Printing first and value here.
}
val=first=value="" ##Nullify variables val, first and value here.
}
' Input_file ##Mentioning Input_file name here.
With two loops:
awk '{
x=$1 OFS $2 OFS $3 # x contains prefix https://
for(i=3; i<=NF; i++) { # NF is number of last element
printf("%s", x) # print prefix
for(j=4; j<=i; j++){
printf("%s%s", OFS, $j) # print / and single element
}
print ""
}
}' FS='/' OFS='/' file
Output:
https://test123.com
https://test123.com/endpoint1
https://test123.com/endpoint1/endpoint2
https://test123.com/endpoint1/endpoint2/endpoint3
https://test456.com
https://test456.com/endpoint1
https://test456.com/endpoint1/endpoint2
https://test456.com/endpoint1/endpoint2/endpoint3
https://test789.com
https://test789.com/endpoint1
https://test789.com/endpoint1/endpoint2
https://test789.com/endpoint1/endpoint2/endpoint3
See: 8 Powerful Awk Built-in Variables – FS, OFS, RS, ORS, NR, NF, FILENAME, FNR
$ awk -F'/' '{ep=$1 FS FS; for (i=3;i<NF;i++) print ep=ep $i FS; print ep $NF}' file
https://test123.com/
https://test123.com/endpoint1/
https://test123.com/endpoint1/endpoint2/
https://test123.com/endpoint1/endpoint2/endpoint3
https://test456.com/
https://test456.com/endpoint1/
https://test456.com/endpoint1/endpoint2/
https://test456.com/endpoint1/endpoint2/endpoint3
https://test789.com/
https://test789.com/endpoint1/
https://test789.com/endpoint1/endpoint2/
https://test789.com/endpoint1/endpoint2/endpoint3
A solution using perl.
perl -F/ -le 'print; while (3 < #F) { pop #F; print join("/", #F, "") }' input_file
Gives the following for your sample input.
https://test123.com/endpoint1/endpoint2/endpoint3
https://test123.com/endpoint1/endpoint2/
https://test123.com/endpoint1/
https://test123.com/
https://test456.com/endpoint1/endpoint2/endpoint3
https://test456.com/endpoint1/endpoint2/
https://test456.com/endpoint1/
https://test456.com/
https://test789.com/endpoint1/endpoint2/endpoint3
https://test789.com/endpoint1/endpoint2/
https://test789.com/endpoint1/
https://test789.com/
See https://perldoc.perl.org/perlrun.html#Command-Switches look for -Fpattern.

Dynamically generated regex for gsub not working

I have an input CSV file:
1,5,1
1,6,2
1,5,3
1,7,4
1,5,5
1,6,6
1,6,7
I need to create a string out of this as follows:
;5,1,3,5;6,2,6,7;7,4
So each character, except the first which is the value of the field $2, in the substring in between the ; denotes the row number of middle field; for example ;5,1,3,5 means that 5 is at row number 1,3,5.
I've been trying to use awk with gsub, trying to create the string MYSTR dynamically.
The regex inside the gsub is not working. I need a regex that will match ;$3 (the value of $3, which can be a two digit number) and replace it with ;$3,RowNO, if the pattern is not matched then add ;$3 at the end of the string.
This is what I have so far:
awk -F',' '{
print NR, $3;
noofchars=gsub(/;$3/,";"$3","NR,MYSTR);
print noofchars;
if ( noofchars == 1 )
;
else
MYSTR=MYSTR";"$3","NR;
print NR, $3;
print MYSTR;
}
END{print MYSTR;}' $1
The regex doesn't work because $3 isn't interpreted as the field #3 value but is seen as the anchor $ (that matches the end of the line) and a literal 3.
You can do it without gsub:
awk -F, '{a[$2]=a[$2]","NR}END{for (i in a){printf(";%d%s",i,a[i])}}'
Input
$ cat file
1,5,1
1,6,2
1,5,3
1,7,4
1,5,5
1,6,6
1,6,7
Output
$ awk -F, '{gsub(/[ ]+/,"",$3);a[$2] = ($2 in a ? a[$2]:$2) FS $3 }END{for(i in a)printf("%s%s",";",a[i]); print ""}' file
;5,1,3,5;6,2,6,7;7,4
Better Readable version
awk -F, '
{
gsub(/[ ]+/,"",$3); # suppress space char in third field
a[$2] = ($2 in a ? a[$2]:$2) FS $3 # array a where index being field2 and value will be field3, if index exists before append string with existing value
}
END{
for(i in a) # loop through array a and print values
printf("%s%s",";",a[i]);
print ""
}
' file
#vsshekhar: Try following too: It will provide you values in the correct same order which Input_file ($2) are coming.
awk -F, '{A[++i]=$2;B[A[i]]=B[A[i]]?B[A[i]] "," FNR:FNR} END{for(j=1;j<=i;j++){if(B[A[j]]){printf(";%s,%s",A[j],B[A[j]]);delete B[A[j]]}};print ""}' Input_file
Adding a non-one liner form of solution too now.
awk -F, '{
A[++i]=$2;
B[A[i]]=B[A[i]]?B[A[i]] "," FNR:FNR
}
END{
for(j=1;j<=i;j++){
if(B[A[j]]){
printf(";%s,%s",A[j],B[A[j]]);
delete B[A[j]]
}
};
print ""
}
' Input_file

how to extract a part of a line after delimiter using awk programming and print it to output?

I want to extract 911116683 and USCAFARES and also display the name of input file which I am processing using awk. the string is: '/split/911116683/1Y/USCAFARES' .
My awk program is :
awk -F" " 'BEGIN { OFS="|"; FILE_NAME="NULL"; WU_SG="NULL"; SRC_FTP_LOG_FILENAME="NULL"; } /Src file:/ { FILE_NAME1=$3; WU_SG1=$3; } END{ FILE_NAME=substr(FILE_NAME1,22,9); WU_SG=substr(FILE_NAME1,9,9); SRC_FTP_LOG_FILENAME=FILENAME; print(FILE_NAME,WU_SG,SRC_FTP_LOG_FILENAME); }' $File_to_be_processed
but I am getting syntax error for echo statement, I guess it can't be used. how to extract the fields here inside awk and display using variables?
my input file i.e File_to_be_processed contains this line:
Src file: '/split/911116683/1Y/USCAFARES' on host 'Local'
my output desired is:
USCAFARES|911116683|SYSOUT_0ranv_00007
where SYSOUT_0ranv_00007 is the name of the input file or File_to_be_processed
Works for me. All I did with your code was add whitespace, remove the needless variable instantiations, and lowercase the awk variable names to make it less shouty.
File_to_be_processed=SYSOUT_0ranv_00007
cat >$File_to_be_processed <<'END'
Src file: '/split/911116683/1Y/USCAFARES' on host 'Local'
END
awk -F" " '
BEGIN { OFS = "|" }
/Src file:/ { file_name1 = $3; wu_sg1 = $3; }
END {
file_name = substr(file_name1,22,9)
wu_sg = substr(file_name1,9,9)
src_ftp_log_filename = FILENAME
print(file_name,wu_sg,src_ftp_log_filename)
}
' $File_to_be_processed
I would use a different FS to make it easier to grab the bits you want:
awk -F "[/']" -v OFS="|" '/Src file:/ {print $6, $4, FILENAME; exit}' $File_to_be_processed