简体   繁体   中英

Print a block of text until the first blank line using awk

here is my sample file:

Host dns2
        HostName 172.20.4.80
        User root
        Port 22

Host dns1
        HostName 172.20.4.75
        User root
        Port 22

Host dns3
        HostName 172.20.4.76
        User root
        Port 22

Host dns4
        HostName 172.20.4.77
        User root
        Port 22

Host dns5
        HostName 172.20.4.78
        User root
        Port 22

Host dns6
        HostName 172.20.4.79
        User root
        Port 22

i want to print only one block means for example

Host dns1
        HostName 172.20.4.75
        User root
        Port 22

output :

Host: dns2  HostName: 172.20.4.80   User: root  Port: 22

but in this example all blocks have 4 lines maybe they reach to 5 or more lines later so i want to print from Host to first blank line or remove from Host to first blank line

im really bad at regex and need this to complete my script

thank you

I think that you basically want this:

awk -v RS='' '/dns1/' file

Unset the record separator so that each block is treated as a record, then print whichever record matches the pattern.

Or to use a shell variable:

host=dns1
awk -v host="$host" -v RS='' '$0 ~ host' file

In both of these examples, I'm using the fact that the default action is { print } . As you will likely be changing the output by using { printf ... } you may want to consider adding an exit statement to avoid processing the rest of the file unnecessarily.

a similar awk

$ awk -v RS= -v OFS=' ' '{for(i=1;i<NF;i+=2) $i=$i":"}1' hosts

Host: dns2 HostName: 172.20.4.80 User: root Port: 22
Host: dns1 HostName: 172.20.4.75 User: root Port: 22
Host: dns3 HostName: 172.20.4.76 User: root Port: 22
Host: dns4 HostName: 172.20.4.77 User: root Port: 22
Host: dns5 HostName: 172.20.4.78 User: root Port: 22
Host: dns6 HostName: 172.20.4.79 User: root Port: 22

will give you all records in the desired output format. You can filter either this output further or add a pattern such as

$ awk -v RS= -v OFS=' ' '{for(i=1;i<NF;i+=2) $i=$i":"} /dns2/' hosts

Host: dns2 HostName: 172.20.4.80 User: root Port: 22

if want to exit after processing the selected record, you need to slightly change the script

$ awk -v RS= -v OFS=' ' '/dns2/{for(i=1;i<NF;i+=2) $i=$i":"; print; exit}' hosts
Host: dns2 HostName: 172.20.4.80 User: root Port: 22

If you want to select everything except one record you can negate the pattern (and remove exit)

$ awk -v RS= -v OFS=' ' '!/dns2/{for(i=1;i<NF;i+=2) $i=$i":"; print}' hosts
Host: dns1 HostName: 172.20.4.75 User: root Port: 22
Host: dns3 HostName: 172.20.4.76 User: root Port: 22
Host: dns4 HostName: 172.20.4.77 User: root Port: 22
Host: dns5 HostName: 172.20.4.78 User: root Port: 22
Host: dns6 HostName: 172.20.4.79 User: root Port: 22

Note that sed inplace replacement needs an intermediary file. If you want to replace the original file with the formatted one sans one record, you can use this command pattern on the last awk statement

$ awk ... > temp && mv temp original

UPDATE: setting OFS will change all the separators between fields. You want to logically group them by name: value , so change the script as such

$ awk -v RS= '{for(i=1;i<NF;i++) $i=$i (i%2?":":"\t")}1' hosts
Host: dns2       HostName: 172.20.4.80   User: root      Port: 22
Host: dns1       HostName: 172.20.4.75   User: root      Port: 22
Host: dns3       HostName: 172.20.4.76   User: root      Port: 22
Host: dns4       HostName: 172.20.4.77   User: root      Port: 22
Host: dns5       HostName: 172.20.4.78   User: root      Port: 22
Host: dns6       HostName: 172.20.4.79   User: root      Port: 22

which sets a tab delimiter after even positioned fields.

Not different than Tom Fenech approach, since it uses the record separator, but it plays with the field separator too to obtain the desired output:

awk -v RS='' -F'\n[\t ]*' -v OFS='  ' '/dns1/{$1=$1;print}' file

When you change the output field separator, you need to use $1=$1 (or $0=$0 or with any other field) to force awk to re-evaluate the record and to take in account the new field separator.

Notes: you can exit awk when a matching block is found with the exit command. This avoids to process all the end of the file. You can also only test the pattern /dns1/ with the first field.

awk -v RS='' -F'\n[\t ]*' -v OFS='  ' '$1~/dns1/{$1=$1;print;exit}' file

If you add semi-colons to the result, since you modify the fields, the $1=$1 trick becomes useless. You can write:

awk -v RS='' -F'\n[\t ]*' -v OFS='  ' '$1~/dns1/{for(i=1;i<=NF;i++){sub(" ", ": ", $i)};print;exit}' file

To print the 3rd record:

$ awk -v RS= -F'\n[[:blank:]]+' -v OFS='\t' 'NR==3{$1=$1; gsub(/ +/,": "); print}' file
Host: dns3      HostName: 172.20.4.76   User: root      Port: 22

To print the records that contains dns4 :

$ awk -v RS= -F'\n[[:blank:]]+' -v OFS='\t' '/dns4/{$1=$1; gsub(/ +/,": "); print}' file
Host: dns4      HostName: 172.20.4.77   User: root      Port: 22

To print all records except those that contain dns3 , dns4 , or dns5 :

$ awk -v RS= -F'\n[[:blank:]]+' -v OFS='\t' '!/dns[345]/{$1=$1; gsub(/ +/,": "); print}' file
Host: dns2      HostName: 172.20.4.80   User: root      Port: 22
Host: dns1      HostName: 172.20.4.75   User: root      Port: 22
Host: dns6      HostName: 172.20.4.79   User: root      Port: 22

This might work for you (GNU sed):

 sed -n '/Host dns1/{:a;N;/^\s*$/M!ba;s/\n\s*/  /g;s/\s*$//p}' file

This focuses in on the required string, then appends following lines until a blank one and finally manipulates the new string that is gathered into the required output.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM