Suppose I have a configuration file that can be in one of two format below (short example, but basically the first format is a line that is too long that you have to use a line continuation character, while the second format is just simply a long line without the line continuation)
data1=x data2=y data3=z \
datakey
second format
data=1 data2=y data3=z datakey
I want to match the exact line data1=x data2=y data3=x datakey for both situation. Is there simple way of doing that?
read interprets \ as the line continuation character:
while read line ; do
if [[ $line == 'data=1 data2=y data3=z datakey' ]] ; then
echo "$line"
fi
done
I would use sed to create an output without the ending \:
sed -e ':begin;/\\$/{N;bbegin};s/\\\n//g' your_file
Then you could grep it:
sed -e ':begin;/\\$/{N;bbegin};s/\\\n//g' your_file | grep your_pattern
You can even do this all in sed:
sed -n -e ':begin;/\\$/{N;bbegin};s/\\\n//g;/your_pattern/p' your_file
UPDATE:
To explain above:
:begin sets a tag to which I can branch (goto) with the b command.
/\\$/{N;bbegin} if the current line ends with a \ (/\\$/), append the next line to the buffer (N) and goto begin (bbegin).
Then, when the lines does not end with a \, remove all the \ and the line break (thes/\\n//g`).
Then -n option tells sed no to print the line at the end of the script.
/your_pattern/p prints the line if it matches your_pattern.
UPDATE2:
We could even do better and show the original lines of your file:
sed -n -e ':begin;/\\$/{N;bbegin};h;s/\\\n//g;/your_pattern/{g;p}' your_file
What this does is before removing the \ and the line break, it saves the data in the hold space (h) and if the line matches, it prints the data that was saved (g copies the hold space to the pattern space that is printed).
Maybe grep is not a best tool for problems like that.
You could join all lines ends with \ and then grep that output as usual:
Suppose you have a file:
$> cat text
1
2
fasdfasdf
data1=x data2=y data3=z \
datakey
fasfd
sdf
So you can join all lines ends with \:
$> awk '{line = $0}; /.*\\/ {split($0,tmp,"\\"); line = tmp[1]; getline; line = line $0}; { print line }' text
1
2
fasdfasdf
data1=x data2=y data3=z datakey
fasfd
sdf
awk -v i="data1=x data2=y data3=z datakey" '{x=x" "$0}END{y=match(x," "i);if(y) print "yes its a match"}' temp
tested below:
> cat temp
data1=x data2=y data3=z
datakey
> awk -v i="data1=x data2=y data3=z datakey" '{x=x" "$0}END{y=match(x," "i);if(y) print "yes its a match"}' temp
yes its a match
>
I like the sed example above, and didn't notice the awk ones already
submitted. Here is awk (nawk) version that keeps the formatting and deals
with multiple continuation lines and end-of-file.
nawk -v re="search-string" \
'{ls=ls""$0;lp=lp""$0}
/\\$/{ls=substr(ls,1,length(ls)-1);lp=lp"\n";next}
ls~re{print lp}
{lp=ls=""}
END{if (ls ~ re)print substr(lp,1,length(lp)-1)}' input-file
Explanation:
re=search-string sets the regular expression to look for.
ls=ls""$0 Concatenate new line to ls (line-search), also store
original line to lp (line-print) as per sed example above.
/\\$/ checks to see if input has continuation character and
if it does substr() removes the extra character from ls, lp has a
newline appended to keep original formatting. Lastly next
causes awk to read the next line and start from the first rule.
ls~i searches line-search for re and if it matches prints lp,
could add a switch to print ls if preferred.
Lastly (not penultimately) reset ls & lp when no continuation line.
END is special rule, used here to detect that ls still has a
value assigned, meaning the continuation line is at the EOF. Searched for the regex, when it
matches it cuts off the additional newline added above.
Now, no one would write '\' at the end of a line would they?
Related
Instead of grep , I used awk here. The file pkg.conf has 'ssl_cipher' string , I need to copy the line containing ssl_cipher to another file 'pkg.conf.new' at the same line number (here it`s 20 in pkg.conf):
bash-4.2$ awk '/ssl_cipher/ {print FNR,$(NF-1),$NF}' pkg.conf
20 ssl_cipher 'ECDHJES128:ECDH+AESGCM:ECDH+AES256:DH+AES:DH+AESGCM:DH+AES256:RSA+AES :RSA+AESGCMHaNULL:!RC4:!MD5:!DSS:!3DESHSSLv3');
Is there an awk one liner to do this or should I seek the help of 'sed' ?
You can use this awk script:
awk 'NR==FNR{ # On the first file
if(/ssl_cipher/){ # lookup the string
line_content=$0; # store the content
line_no=NR # and line number
};
next # skip other files
}
FNR==line_no{ # On the second file, at the wanted line
print line_content # append the wanted content
}1 # print the other lines
' pkg.conf pkg-new.conf
Note that will insert a new line. As mentionned by #Yoric, if you want to replace the line, and the next keyword after the print line_content.
The result is output to the stdout. If you want to replace the pkg-new.conf file, and if you have GNU awk, you can add the option -i inplace to the command line.
You can use sed to generate a sed script:
awk '/ssl_cipher/ {print FNR,$(NF-1),$NF}' pkg.conf \
| sed 's/ /{i/;s/$/\nd}/' \
| sed -f- pkg.conf.new
The first sed script will transform the output to
20{issl_cipher 'ECDHJES128:ECDH+AESGCM:ECDH+AES256:DH+AES:DH+AESGCM:DH+AES256:RSA+AES :RSA+AESGCMHaNULL:!RC4:!MD5:!DSS:!3DESHSSLv3');
d}
Which tells sed to treat line 20 specially: i is for insert, and d for delete which removes the original contents of the line.
I have a file that have a list of integers:
12542
58696
78845
87855
...
I want to change them into:
"12542", "58696", "78845", "87855", "..."
(no comma at the end)
I believe I need to use sed but couldnt figure it out how. Appreciate your help.
You could do a sed multiline trick, but the easy way is to take advantage of shell expansion:
echo $(sed '$ ! s/.*/"&",/; $ s/.*/"&"/' foo.txt)
Run echo $(cat file) to see why this works. The trick, in a nutshell, is that the result of cat is parsed into tokens and interpreted as individual arguments to echo, which prints them separated by spaces.
The sed expression reads
$ ! s/.*/"&",/
$ s/.*/"&"/
...which means: For all but the last line ($ !) replace the line with "line",, and for the last line, with "line".
EDIT: In the event that the file contains not just a line of integers like in OP's case (when the file can contain characters the shell expands), the following works:
EDIT2: Nicer code for the general case.
sed -n 's/.*/"&"/; $! s/$/,/; 1 h; 1 ! H; $ { x; s/\n/ /g; p; }' foo.txt
Explanation: Written in a more readable fashion, the sed script is
s/.*/"&"/
$! s/$/,/
1 h
1! H
$ {
x
s/\n/ /g
p
}
What this means is:
s/.*/"&"/
Wrap every line in double quotes.
$! s/$/,/
If it isn't the last line, append a comma
1 h
1! H
If it is the first line, overwrite the hold buffer with the result of the previous transformation(s), otherwise append it to the hold buffer.
$ {
x
s/\n/ /g
p
}
If it is the last line -- at this point the hold buffer contains the whole line wrapped in double quotes with commas where appropriate -- swap the hold buffer with the pattern space, replace newlines with spaces, and print the result.
Here is the solution,
sed 's/.*/ "&"/' input-file|tr '\n' ','|rev | cut -c 2- | rev|sed 's/^.//'
First change your input text line in quotes
sed 's/.*/ "&"/' input-file
Then, this will convert your new line to commas
tr '\n' ',' <your-inputfile>
The last commands including rev, cut and sed are used for formatting the output according to requirement.
Where,
rev is reversing string.
cut is removing trailing comma from output.
sed is removing the first character in the string to formatting it accordingly.
Output:
With perl without any pipes/forks :
perl -0ne 'print join(", ", map { "\042$_\042" } split), "\n"' file
OUTPUT:
"12542", "58696", "78845", "87855"
Here's a pure Bash (Bash≥4) possibility that reads the whole file in memory, so it won't be good for huge files:
mapfile -t ary < file
((${#ary[#]})) && printf '"%s"' "${ary[0]}"
((${#ary[#]}>1)) && printf ', "%s"' "${ary[#]:1}"
printf '\n'
For huge files, this awk seems ok (and will be rather fast):
awk '{if(NR>1) printf ", ";printf("\"%s\"",$0)} END {print ""}' file
One way, using sed:
sed ':a; N; $!ba; s/\n/", "/g; s/.*/"&"/' file
Results:
"12542", "58696", "78845", "87855", "..."
You can write the column oriented values in a row with no comma following the last as follows:
cnt=0
while read -r line || test -n "$line" ; do
[ "$cnt" = "0" ] && printf "\"%s\"" "$line"
printf ", \"%s\"" "$line"
cnt=$((cnt + 1))
done
printf "\n"
output:
$ bash col2row.sh dat/ncol.txt
"12542", "12542", "58696", "78845", "87855"
A simplified awk solution:
awk '{ printf sep "\"%s\"", $0; sep=", " }' file
Takes advantage of uninitialized variables defaulting to an empty string in a string context (sep).
sep "\"%s\"" synthesizes the format string to use with printf by concatenating sep with \"%s\". The resulting format string is applied to $0, each input line.
Since sep is only initialized after the first input record, , is effectively only inserted between output elements.
I want to add a text to the end of the first line of a file using a bash script.
The file is /etc/cmdline.txt which does not allow line breaks and needs new commands seperated by a blank, so text i want to add realy needs to be in first line.
What i got so far is:
line=' bcm2708.w1_gpio_pin=20'
file=/boot/cmdline.txt
if ! grep -q -x -F -e "$line" <"$file"; then
printf '%s' "$line\n" >>"$file"
fi
But that appends the text after the line break of the first line, so the result is wrong.
I either need to trim the file contend, add my text and a line feed or somehow just add it to first line of file not touching the rest somehow, but my knowledge of bash scripts is not good enough to find a solution here, and all the examples i find online add beginning/end of every line in a file, not just the first line.
This sed command will add 123 to end of first line of your file.
sed ' 1 s/.*/&123/' yourfile.txt
also
sed '1 s/$/ 123/' yourfile.txt
For appending result to the same file you have to use -i switch :
sed -i ' 1 s/.*/&123/' yourfile.txt
This is a solution to add "ok" at the first line on /etc/passwd, I think you can use this in your script with a little bit of 'tuning' :
$ awk 'NR==1{printf "%s %s\n", $0, "ok"}' /etc/passwd
root:x:0:0:root:/root:/bin/bash ok
To edit a file, you can use ed, the standard editor:
line=' bcm2708.w1_gpio_pin=20'
file=/boot/cmdline.txt
if ! grep -q -x -F -e "$line" <"$file"; then
ed -s "$file" < <(printf '%s\n' 1 a "$line" . 1,2j w q)
fi
ed's commands:
1: go to line 1
a: append (this will insert after the current line)
We're in insert mode and we're inserting the expansion of $line
.: stop insert mode
1,2j join lines 1 and 2
w: write
q: quit
This can be used to append a variable to the first line of input:
awk -v suffix="$suffix" '{print NR==1 ? $0 suffix : $0}'
This will work even if the variable could potentially contain regex formatting characters.
Example:
suffix=' [first line]'
cat input.txt | awk -v suffix="$suffix" '{print NR==1 ? $0 suffix : $0}' > output.txt
input.txt:
Line 1
Line 2
Line 3
output.txt:
Line 1 [first line]
Line 2
Line 3
I want to add Some large code between two patterns:
File1.txt
This is text to be inserted into the File.
infile.txt
Some Text here
First
Second
Some Text here
I want to add File1.txt content between First and Second :
Desired Output:
Some Text here
First
This is text to be inserted into the File.
Second
Some Text here
I can search using two patterns with sed command ,But I don't have idea how do I add content between them.
sed '/First/,/Second/!d' infile
Since /r stands for reading in a file, use:
sed '/First/r file1.txt' infile.txt
You can find some info here: Reading in a file with the 'r' command.
Add -i (that is, sed -i '/First/r file1.txt' infile.txt) for in-place edition.
To perform this action no matter the case of the characters, use the I mark as suggested in Use sed with ignore case while adding text before some pattern:
sed 's/first/last/Ig' file
As indicated in comments, the above solution is just printing a given string after a pattern, without taking into consideration the second pattern.
To do so, I'd go for an awk with a flag:
awk -v data="$(<patt_file)" '/First/ {f=1} /Second/ && f {print data; f=0}1' file
Given these files:
$ cat patt_file
This is text to be inserted
$ cat file
Some Text here
First
First
Second
Some Text here
First
Bar
Let's run the command:
$ awk -v data="$(<patt_file)" '/First/ {f=1} /Second/ && f {print data; f=0}1' file
Some Text here
First # <--- no line appended here
First
This is text to be inserted # <--- line appended here
Second
Some Text here
First # <--- no line appended here
Bar
i think you can try this
$ sed -n 'H;${x;s/Second.*\n/This is text to be inserted into the File\
&/;p;}' infile.txt
awk flavor:
awk '/First/ { print $0; getline < "File1.txt" }1' File2.txt
Here's a cut of bash code that I wrote to insert a pattern from patt_file. Essentially had had to delete some repetitious data using uniq then add some stuff back in. I copy the stuff I need to put back in using lineNum values, save it to past_file. Then match patMatch in the file I'm adding the stuff to.
#This pulls the line number from row k, column 2 of the reduced repitious file
lineNum1=$(awk -v i=$k -v j=2 'FNR == i {print $j}' test.txt)
#This pulls the line number from row k + 1, coulmn 2 of the reduced repitious file
lineNum2=$(awk -v i=$((k+1)) -v j=2 'FNR == i {print $j}' test.txt)
#This pulls fields row 4, 2 and 3 column into with tab spacing (important) from reduced repitious file
awk -v i=$k -v j=2 -v h=3 'FNR == i {print $j" "$h}' test.txt>closeJ.txt
#This substitutes all of the periods (dots) for \. so that sed will match them
patMatch=$(sed 's/\./\\./' closeJ.txt)
#This Selects text in the full data file between lineNum1 and lineNum2 and copies it to a file
awk -v awkVar1=$((lineNum1 +1)) -v awkVar2=$((lineNum2 -1)) 'NR >= awkVar1 && NR <= awkVar2 { print }' nice.txt >patt_file.txt
#This inserts the contents of the pattern matched file into the reduced repitious file
#The reduced repitious file will now grow
sed -i.bak "/$patMatch/ r "patt_file.txt"" test.txt
Is it possible to use grep to match only lines with numbers in a pre-specified range?
For instance I want to list all lines with numbers in the range [1024, 2048] of a log that contain the word 'error'.
I would like to keep the '-n' functionality i.e. have the number of the matched line in the file.
Use sed first:
sed -ne '1024,2048p' | grep ...
-n says don't print lines, 'x,y,p' says print lines x-y inclusive (overrides the -n)
sed -n '1024,2048{/error/{=;p}}' | paste - -
Here /error/ is a pattern to match and = prints the line number.
Awk is a good tool for the job:
$ awk 'NR>=1024 && NR<=2048 && /error/ {print NR,$0}' file
In awk the variable NR contains the current line number and $0 contains the line itself.
The benefits with using awk is that you can easily change the output to display however you want it. For instance to separate the line number from line with a colon followed by a TAB:
$ awk 'NR>=1024 && NR<=2048 && /error/ {print NR,$0}' OFS=':\t' file