bash- remove \n every three lines - bash

How can I remove newline delimiter from every three lines.
Example:
input:
1
name
John
2
family
Grady
3
Tel
123456
output:
1
name John
2
family Grady
3
Tel 123456

This might work for you (GNU sed):
sed 'n;N;s/\n//' file
to replace the newline with a space use:
sed 'n:N;s/\n/ /' file
as an alternative use paste:
paste -sd'\n \n' file

One way using AWK:
awk '{ printf "%s%s", $0, (NR%3==2 ? FS : RS) }' file

awk 'NR%3==2{printf "%s ",$0;next}{print $0}' input.txt
Output:
1
name John
2
family Grady
3
Tel 123456

You could do this in Perl,
$ perl -pe 's/\n/ /g if $. % 3 == 2' file
1
name John
2
family Grady
3
Tel 123456

Assuming all those lines you want joined with the next one end with : (your original question):
1
name:
John
2
family: Grady
3
Tel:
123456
You can use sed for this, with:
sed ':a;/:$/{N;s/\n//;ba}'
The a is a branch label. The pattern :$ (colon at end of line) is detected and, if found, N appends the next line to the current one, the newline between them is removed with the s/\n// substitution command, and it branches back to label a with the ba command.
For your edited question where you just want to combine the second and third line of each three-line group regardless of content:
1
name
John
2
family
Grady
3
Tel
123456
Use:
sed '{n;N;s/\n/ /}'
In that command sequence, n will output the first line in the group and replace it with the second one. Then N will append the third line to that second one and s/\n/ / will change the newline between them into a space before finally outputting the combined two-three line.
Then it goes onto the next group of three and does the same thing.
Both those commands will generate the desired output for their respective inputs.

Yet another solution, in Bash:
while read line
do
if [[ $line = *: ]]
then
echo -n $line
else
echo $line
fi
done < input.txt

Unix way:
$ paste -sd'\n \n' input
Output:
1
name John
2
family Grady
3
Tel 123456

Related

Find a line with a single word and merge it with the next line

I have an issue with grep that i can't sort out.
What I have.
A listing of firstnames and lastnames, like:
John Doe
Alice Smith
Bob Smith
My problem.
Sometimes, firstname and lastname are disjointed, like:
Alice
Smith
Bob Doolittle
Mark
Von Doe //sometimes, there are more than one word on the next line
What I'd like to achieve.
Concatenate the "orphan" name with the next line.
Alice Smith
Bod Doolittle
Mark Von Doe
What I already tried
grep -ozP "^\w+\n\w.+" file | tr '\n' ' '
So, here I ask grep to find a line with just one word and concatenate it with the following line, even is this next line has more than one word.
It works correctly but only if the isolated word is at the very beginning of the file. If it appears below the first line, grep do not spot it. So a quick and dirty solution where I would loop through the file and remove a line after each pass doesn't work for me.
If awk is acceptable:
awk '
NF==1 {printf "%s ",$1; getline; print; next}
1' names.dat
Where:
NF==1 - if only one name/field in the current record ...
printf / getline / print / next - print field #1, read next line and print it, then skip to next line
1 - print all other lines as is
As a one-liner:
awk 'NF==1{printf "%s ",$1;getline;print;next}1' names.dat
This generates:
Alice Smith
Bob Doolittle
Mark Von Doe //sometimes, there are more than one word on the next line
You can use GNU sed like this:
sed -E -i '/^[^[:space:]]+$/{N;s/\n/ /}' file
See the sed demo:
s='Alice
Smith
Bob Doolittle
Mark
Von Doe //sometimes, there are more than one word on the next line'
sed -E '/^[^[:space:]]+$/{N;s/\n/ /}' <<< "$s"
Output:
Alice Smith
Bob Doolittle
Mark Von Doe //sometimes, there are more than one word on the next line
Details:
/^[^[:space:]]+$/ finds a line with no whitespace
{N;s/\n/ /} - reads in the next line, and appends a newline char with this new line to the current pattern space, and then s/\n/ / replaces this newline char with a space.
Use this Perl one-liner:
perl -lane 'BEGIN { $is_first_name = 1; } if ( #F == 1 && $is_first_name ) { #prev = #F; $is_first_name = 0; } else { print join " ", #prev, #F; $is_first_name = 1; #prev = (); }' in_file
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.
Using awk:
awk '
{f=$2 ? 1 : 0}
v==1{v=0; print; next}
f==0{v=1; printf "%s ", $1; next}
1
' file
Output
Alice Smith
Bob Doolittle
Mark Von Doe
This might work for you (GNU sed):
sed -E 'N;s/^(\S+)\n/\1 /;P;D' file
Append the next line.
If the first line in the pattern space contains one word only, replace the following newline with a space.
Print/delete the first line and repeat.

Replace a word of a line if matched

I am given a file. If a line has "xxx" as its third word then I need to replace it with "yyy". My final output must have all the original lines with the modified lines.
The input file is-
abc xyz mno
xxx xyz abc
abc xyz xxx
abc xxx xxx xxx
The required output file should be-
abc xyz mno
xxx xyz abc
abc xyz yyy
abc xxx yyy xxx
I have tried-
grep "\bxxx\b" file.txt | awk '{if ($3=="xxx") print $0;}' | sed -e 's/[^ ]*[^ ]/yyy/3'
but this gives the output as-
abc xyz yyy
abc xxx yyy xxx
Following simple awk may help you in same.
awk '$3=="xxx"{$3="yyy"} 1' Input_file
Output will be as follows.
abc xyz mno
xxx xyz abc
abc xyz yyy
abc xxx yyy xxx
Explanation: Checking condition here if $3 3rd field is equal to string xxx then setting $3's value to string yyy. Then mentioning 1 there, since awk works on method of condition then action. I am making condition TRUE here by mentioning 1 here and NOT mentioning any action here so be default print of current line will happen(either with changed 3rd field or with new 3rd field).
sed solution:
sed -E 's/^(([^[:space:]]+[[:space:]]+){2})apathy\>/\1empathy/' file
The output:
abc xyz mno
apathy xyz abc
abc xyz empathy
abc apathy empathy apathy
To modify the file inplace add -i option: sed -Ei ....
In general the awk command may look like
awk '{command set 1}condition{command set 2}' file
The command set 1 would be executed for every line while command set 2 will be executed if the condition preceding that is true.
My final output must have all the original lines with the modified
lines
In your case
awk 'BEGIN{print "Original File";i=1}
{print}
$3=="xxx"{$3="yyy"}
{rec[i++]=$0}
END{print "Modified File";for(i=1;i<=NR;i++)print rec[i]}'file
should solve that.
Explanation
$3 is the the third space-delimited field in awk. If it matches "xxx", then it is replaced. Print the unmodified lines first while storing the modified lines in an array. At the end, print the modified lines. BEGIN and END blocks are executed only at the beginning and the end respectively. NR is the awk built-in variable which denotes that number of records processed till the moment. Since it is used in the END block it should give us the total number of records.
All good :-)
Ravinder has already provided you with the shortest awk solution possible.
In sed, the following would work:
sed -E 's/(([^ ]+ ){2})xxx/\1yyy/'
Or if your sed doesn't include -E, you can use the more painful BRE notation:
sed 's/\(\([^ ][^ ]* \)\{2\}\)xxx/\1yyy/'
And if you're in the mood to handle this in bash alone, something like this might work:
while read -r line; do
read -r -a a <<<"$line"
[[ "${a[2]}" == "xxx" ]] && a[2]="yyy"
printf '%s ' "${a[#]}"
printf '\n'
done < input.txt

unique file based on 2 line match

I have a file that has lines like this. I'd like to uniq this where every unique item consists of the 2 lines. so since
bob
100
is here twice I would only print it the one time. Help please. thanks,
bob
100
bill
130
joe
123
bob
100
joe
120
Try this:
printf "%s %s\n" $(< file) | sort -u | tr " " "\n"
Output:
bill
130
bob
100
joe
120
joe
123
With bash builtins:
declare -A a # declare associative array
while read name; do read value; a[$name $value]=; done < file
printf "%s\n" ${!a[#]} # print array keys
Output:
joe
120
joe
123
bob
100
bill
130
Try sed:
sed 'N;s/\n/ /' file | sort -u | tr ' ' '\n'
N: read next line, and append to current line
;: command separator
s/\n/ /: replace eol with space
I would use awk:
awk 'NR%2{l=$0}!(NR%2){seen[l"\n"$0]}END{for(i in seen)print i}' input
Let me explain the command in a multi-line version:
# On odd lines numbers store the current line in l.
# Note that line numbers starting at 1 in awk
NR%2 {l=$0}
# On even line numbers create an index in a associative array.
# The index is the last line plus the current line.
# Duplicates would simply overwrite themselves.
!(NR%2) {seen[l"\n"$0]}
# After the last line of input has been processed iterate
# through the array and print the indexes
END {for(i in seen)print i}

How can I insert a line with a new line appended to a file using sed on mac?

I want to insert a text line, lets say "hello" to the 3rd line of the file. And there should be a new line appended:
1st
2nd
Hello
3rd
How can I do that?
Very straightforward with awk:
$ cat file
1
2
3
4
5
6
$ awk 'NR==3{print "hello\n"}1' file
1
2
hello
3
4
5
6
Where NR is the line number. You can set it to any number you wish to insert text to.
Does it have to be sed?
head -2 infile ; echo Hello ; echo ; tail +3 infile
$ sed '3s/^/Hello\n\n/' file.txt
1st
2nd
Hello
3rd
The 3 at the beginning of the sed command specifies that the command should be applied to line 3 only. Thus, the command, 3s/^/Hello\n\n/, substitutes in "Hello" and two new lines to the beginning (^ matches the beginning of a line) of line 3. Otherwise, the file is left unchanged.
sed '3 i\
Hello\
' YopurFile
Insert following line (preceded by \) at line 3

Print specific line number only if match in sed

How do you print a (specific) line only if there is a match in sed (Linux stream editor)? Let's say I have line three that I would like to print only if it meets the match criteria; how can I print this?
I am piping a command's output to sed, and would prefer not to use sed's output to pipe to sed again:
| sed -ne ''"$currline"'p' | sed -n '/state/p'`
Also, I was assigning the output to a variable with backticks.
Given inputs A and B, and the search pattern state, the output for A should be the line 3 stateless (note that 3 is part of the data), and for B should be nothing:
Input A Input B
1 state 1 state
2 statement 2 statement
3 stateless 3 statless
4 stated 4 stated
5 estate 5 estate
sed -n '3{/state/p;}' $file
The 3 matches line 3; the actions on line 3 are 'if you find /state/, print'; the -n prevents general printing of lines.
Also, you should avoid backticks; it is better to use the var=$(cmd1 | cmd2) notation than var=`cmd1 | cmd2` notation.
As I understand you want to match two criterias, a specific line number an a pattern. You can achieve it in one sed command.
Assuming infile with content:
one
two
three
four
five
And if you want to print the fourth line if it matches ur, use:
sed -ne '4 { /ur/ { p; q } }' infile
That yields:
four
sed is an excellent tool for simple substitutions on a single line but for anything else just use awk:
awk 'NR==3 && /state/'
With awk :
awk 'NR==3 && /pattern/{print;exit}'
NOTE
pattern is a regex
exit avoid to parse the whole file
This might work for you (GNU sed):
sed '3!d;/state/q;Q' file
or
sed -ne '3!b' -e '/state/p' -e 'q' file

Resources