3

I have this ssh config that needs to be edited.

Host vps6
   HostName 123.456.789.00
   User dylan
   Port 123

Host vps4
   HostName 123.456.789.00 
   User dylan
   Port 123

# old server

Host vps3-old
   HostName 123.456.789.00
   User dylan 
   Port 123

I want to move config for vps6 to end of file and append -old to its config alias. The resulting file would be.

Host vps4
   HostName 123.456.789.00 
   User dylan
   Port 123

# old server

Host vps3-old
   HostName 123.456.789.00
   User dylan 
   Port 123

Host vps6-old
   HostName 123.456.789.00
   User dylan
   Port 123

I managed to do exactly that using this sed command → sed '/'"vps4"'/{N;N;N;N;H;$!d}; ${p;x;s/'"vps4"'/'"vps4"'-old/}', unfortunately this gives me unwanted newline at the end of file.

[tmp]$ sed '/'"vps6"'/{N;N;N;N;H;$!d}; ${p;x;s/'"vps6"'/'"vps6"'-old/}' config
Host vps4
   HostName 123.456.789.00
   User dylan
   Port 123

# old server

Host vps3-old
   HostName 123.456.789.00
   User dylan 
   Port 123

Host vps6-old
   HostName 123.456.789.00 
   User dylan
   Port 123

[tmp]$ # See above me

Moreover, I want to be able to specify the next n line to be moved (for example above will be mark Host vps4 and next 3 line to be moved to end of file). I have searched up the net and found out that the recommended tools for this kind of task is ed, but I have yet to find out the example command to do exactly what I want.

Liso
  • 188
  • 2
  • 14

6 Answers6

5

With your shown samples please try following awk code.

awk '
!NF && found{
  found=""
  next
}
/^Host vps6/{
  found=1
  line=$0"-old"
  next
}
found{
  val=(val?val ORS:"")$0
  next
}
!found
END{
  print ORS line ORS val
}
'  Input_file

NOTE: In case you want to save output into Input_file itself then run above program it will print output on terminal and once you are Happy with results of above program then you can append > temp && mv temp Input_file to above program, to do inplace save into Input_file.

Explanation: Adding detailed explanation for above used code.

awk '                      ##Starting awk program from here.
!NF && found{              ##Checking if line is empty AND found is SET then do following.
  found=""                 ##Nullifying found here.
  next                     ##next will skip all further statements from here.
}
/^Host vps6/{              ##If line starts from Host vps6 then do following.
  found=1                  ##Setting found here.
  line=$0"-old"
  next
}
found{                     ##If found is set then do following.
  val=(val?val ORS:"") $0  ##Creating val which is keep adding current line into it.
  next                     ##next will skip all further statements from here.
}
!found                     ##If found is NOT set then print that line.
END{                       ##Starting END block of this program from here.
  print ORS line ORS val   ##Printing ORS line ORS and val here.
}
'  Input_file              ##Mentioning Input_file name here. 
RavinderSingh13
  • 130,504
  • 14
  • 57
  • 93
  • This did it, but can you add `-old` suffix to moved block ? – Liso Sep 14 '22 at 02:59
  • @Liso, `awk '/^# old server/{line=$0;next} !NF && found{found="";next} /^Host vps6/{found=1} found{val=(val?val ORS:"")$0;next} !found; END{print ORS line ORS val}' Input_file` could you please try this one, this will move the `old suffix` line before vps6 block. Once you confirm its working I will change the answer then, cheers. – RavinderSingh13 Sep 14 '22 at 03:07
  • I'm sorry I have explained it poorly, so what I want to do is move vps6 config to bottom of file (your awk code have done this), and then change line `Host vps6` to `Host vps6-old`. On my question, I have append (bizarre) working solution with sed that also include `-old` suffix. – Liso Sep 14 '22 at 03:08
  • @Liso, Not an issue. Please try `awk '!NF && found{found="";next} /^Host vps6/{found=1;line=$0"-old";next} found{val=(val?val ORS:"")$0;next} !found; END{print ORS line ORS val}' Input_file` once and let me know how it goes. – RavinderSingh13 Sep 14 '22 at 03:09
  • @Liso, Have updated answer and explanation also with same, cheers. – RavinderSingh13 Sep 14 '22 at 03:12
  • Awesome, one more little thing. How would I use variable as input, I'm trying to follow apply this https://unix.stackexchange.com/a/120806 but the awk syntax fail because of this line `$0 ~ ^sshname` producing `awk: line 6: syntax error at or near ^` (`sshname` is variable containing e.g. `vps6`) – Liso Sep 14 '22 at 03:21
  • @Liso, have it like: `awk -v bar="$shellVar" '` for variable. And for condition check do a, ` $0 ~ var` let me know how it goes. – RavinderSingh13 Sep 14 '22 at 03:23
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/248030/discussion-between-liso-and-ravindersingh13). – Liso Sep 14 '22 at 03:28
  • @Liso, To pass values from shell variable to `awk` try: `shellVar="Host vps6"; awk -v var1="$shellVar" '!NF && found{found="";next} $0 ~ "^" var1"$"{found=1;line=$0"-old";next} found{val=(val?val ORS:"")$0;next} !found; END{print ORS line ORS val}' Input_file` once. – RavinderSingh13 Sep 14 '22 at 03:38
3

Another awk:

$ awk -v RS= '{                   # read blank line separated records
    if(/^Host vps6/) {            # buffer matching record
        sub(/vps6/,"vps6-old")    # add -old
        b=$0
    } else                        # print non-matching
        print $0 ORS              # with with extra newline
}
END {                             # in the end
    print b                       # output buffer
}' file

if [you] wanted to use variable instead of hardcoded string, use something like this:

$ awk -v s=vps6 -v RS= '{         # or -v s="$sshname"
    if($0~"^Host " s) {           
        match($0,s)
        b=substr($0,1,RSTART+RLENGTH-1) "-old" substr($0,RSTART+RLENGTH)
    } else
        print $0 ORS
}
END {
    print b
}
James Brown
  • 36,089
  • 7
  • 43
  • 59
  • What if I wanted to use variable instead of hardcoded string ? Instead of `vps6` use `$sshname` which has value of `vps6`. – Liso Sep 14 '22 at 04:31
2

Edit: this is basically the same as @RavinderSingh13's answer, but not as good.


Another potential option:

cat file1
Host vps6
   HostName 123.456.789.00
   User dylan
   Port 123

Host vps4
   HostName 123.456.789.00
   User dylan
   Port 123

# old server

Host vps3-old
   HostName 123.456.789.00
   User dylan
   Port 123
awk '
{flag=0}
/vps6/,/^$/ {flag=1}
{
    gsub("vps6", "vps6-old")
    if (flag == 0) print
    if (flag == 1) a[NR]=$0
}
END {
    print ""
    for (i in a) {
        if(a[i] != "") print a[i]
    }
}' file1

Host vps4
   HostName 123.456.789.00
   User dylan
   Port 123

# old server

Host vps3-old
   HostName 123.456.789.00
   User dylan
   Port 123

Host vps6-old
   HostName 123.456.789.00
   User dylan
   Port 123
jared_mamrot
  • 22,354
  • 4
  • 21
  • 46
2

This might work for you (GNU sed):

sed '/Host vps6/{s//&-old/;:a;N;/\n$/!ba;H;d};$G' file

Match vps6 and append -old.

Gather it up and the following lines until a spaced line, then copy those lines to the hold space (use H rather than h so as to prepend a newline).

Delete those lines.

At the end of the file append the copies.

If the empty line at the end of the file bothers you:

sed '/Host vps6/{s//&-old/;:a;N;s/\n$//;Ta;H;d};$G' file
potong
  • 55,640
  • 6
  • 51
  • 83
  • If there was `Host vps6` and `Host rvps6`, this would match both. Is there any way to only parse `Host vps6` ? – Liso Sep 15 '22 at 01:43
  • @Liso it would not match both, it matches `Host vps6` only. To match both the regexp would have to be `Host r\?vps6`. – potong Sep 15 '22 at 08:13
1

Here is a gnu-awk solution that won't rely on extra line breaks between each Host record:

awk -v RS='(^|\n)Host ' -v s='vps6' '
$1 == s {
   sub(s, s "-old")
   rec = RT $0
   next
}
{ORS=RT}
1
END {print rec}' file

Host vps4
   HostName 123.456.789.00
   User dylan
   Port 123

# old server

Host vps3-old
   HostName 123.456.789.00
   User dylan
   Port 123

Host vps6-old
   HostName 123.456.789.00
   User dylan
   Port 123
anubhava
  • 761,203
  • 64
  • 569
  • 643
0

With ed something like:

#!/bin/sh

sshname="$1"
file=ssh.config

if value=$(grep -- "$sshname" "$file"); then
  case $value in
    *-old)
    printf >&2 '%s is %s already!\n' "$sshname" "'$value'"
    exit 1;;
  esac
else
  printf >&2 '%s is not found in %s!\n' "$sshname" "$file"
  exit 1
fi

ed -s -- "$file" <<EOF
\$a

.
g/$sshname/s/$/-old/
//kx
'x-;/Port/m$
/^$/d
%p
Q
EOF

Now you can do something like:

./myscript vps6
Jetchisel
  • 7,493
  • 2
  • 19
  • 18