how to split a string in two then assign each segment into a variable

1

0

I have this code:

#!/bin/sh
echo "--------- Backup config files -------------"
find /vmfs/volumes/datastore1/ \
   -type f \
   -regex '.*\.\(vmx\|nvram\|vmsd\|vmdk\)$' \
 ! -name '*-flat.vmdk' \
   -exec sh -c "$(cat << 'EOF'
addrs=XX.XX.XX.XX
for pth; do
   drctry="$(printf '%s' "${pth%/*}" | sed "s/'/'\"'\"'/g")"
   ssh "root@$addrs" "mkdir -p '$drctry'" && scp -pr "$pth" "$addrs:'$drctry/'"
done
EOF
   )" sh {} +
echo "----------- Backup VM disks ---------------"
vim-cmd vmsvc/getallvms | sed -e '1d' -e 's/ \[.*$//' | while read -r line; do 
    addrs=XX.XX.XX.XX
    vmid="$(printf '%s' "${line%}" | awk '{print $1;}'| sed "s/'/'\"'\"'/g")"
    vmname="$(printf '%s' "${line%}" | awk '{for (i=2; i<=NF; i++) print $i}'| sed "s/'/'\"'\"'/g")"
    vim-cmd vmsvc/snapshot.create $vmid backup 'Snapshot created by Backup Script' 0 0
    scp -pr "/vmfs/volumes/datastore1/$vmname/$vmname-flat.vmdk" $addrs:"/vmfs/volumes/datastore1/$vmname"
    vim-cmd vmsvc/snapshot.removeall $vmid
done

the first part (backup config files) works flawless. the second part (backup VM disks) doesn't. The output of vim-cmd line (which starts the loop) is:

1      VM_1           
10     VM_2 with_space 
11     VM_3 with space 
12     VM_4 with_space 
14     VM_5 

i need to:

  • assign first word (the numeric id) to var $vmid
  • assign the rest of words to var $vmname so that i can concatenate the path for scp command and run it succesfully without bumping into problems generated by spaces in dir name and file name

At this time i get success for all VMs that doesn't have space in name. For the others i get:

Create Snapshot:
/vmfs/volumes/datastore1/VM_name
with_space/VM_name
with_space.vmdk: No such file or directory
sh: with_space: not found
Remove All Snapshots:

UPDATE 1: For those who need to know the output of "vim-cmd vmsvc/getallvm s", there it is:

Vmid   Name            File                                             Guest OS               Version       Annotation
1      VM_1            [datastore1] VM_1/VM_1.vmx                       winXPProGuest           vmx-13
10     VM_2 with_space [datastore1] VM_2 with_space/VM_2 with_space.vmx opensuse64Guest         vmx-13
11     VM_3 with space [datastore1] VM_3 with space/VM_3 with space.vmx opensuse64Guest         vmx-13
12     VM_4 with_space [datastore1] VM_4 with_space/VM_4 with_space.vmx opensuse64Guest         vmx-13
14     VM_5            [datastore1] VM_5/VM_5.vmx                       winXPProGuest           vmx-13

UPDATE 2:

Thanx to @Sorin i got this monster reduced by more than 60% to 8 lines. So, this is the poor man's cloning solution of a free ESXi 6.5 host to another host:

#!/bin/sh
addrs=XXX.XXX.XXX.XXX
IFS="\t" vim-cmd vmsvc/getallvms | sed -e '1d' -e 's/ \[.*//g' -e "s/\s\+/\t/" | while read id name; do 
    echo "----- Backup "$name" --------"
    scp -pr "/vmfs/volumes/datastore1/$name/$name.vmx" "/vmfs/volumes/datastore1/$name/$name.nvram" "/vmfs/volumes/datastore1/$name/$name.vmsd" "/vmfs/volumes/datastore1/$name/$name.vmdk" $addrs:"'/vmfs/volumes/datastore1/$name'"
    vim-cmd vmsvc/snapshot.create $id backup 'Snapshot created by Backup Script' 0 0
    scp -pr "/vmfs/volumes/datastore1/$name/$name-flat.vmdk" $addrs:"'/vmfs/volumes/datastore1/$name'"
    vim-cmd vmsvc/snapshot.removeall $id
done

Thank you !!

Costin

Posted 2019-09-24T12:25:39.750

Reputation: 15

There's several way to do that but without example of the output of vim-cmd vmsvc/getallvms it's quite hard to help you ... – binarym – 2019-09-24T12:33:00.867

it's not that important, the whole line output is the actual output of the script that is already mentioned (the command gives another 2 columns that provide unnecessary info and i scrap those columns with sed) – Costin – 2019-09-24T12:44:23.610

If i request a sample, it's because it's important in my opinion. Your're doing several sed that can probably be hugely simplified ... if we know the original output. Without it, i can't and won't help. As you wish ... – binarym – 2019-09-24T12:56:17.380

well, if you insist... i added to the post. Thanx. – Costin – 2019-09-24T13:08:14.430

It seems that it is vim-cmd vmsvc/snapshot.create that is failing? Which means the script you posted is irrelevant to the problem? (As $vmid is the only variable that gets passed to it and there shouldn't be problem with it? Which you can confirm easily with an echo since it should be a number only anyway?) – Tom Yan – 2019-09-24T14:39:58.197

@Tom Yan, no. It takes the snapshot. vim-cmd vmsvc/snapshot.create does NOT generate any output (it's a process that is seen only on vSphere). The error is generated by scp command, because of the spaces. Please, concentrate only on the string commands and scp. The others, vmware-related are my area of expertise and all are fine. – Costin – 2019-09-24T15:04:23.730

Well actually it might be easier to debug it yourself because of all your secrecy about the vm names. You can easily isolate whether all the errors are triggered by the scp line by wrapping it with two echoes. (The output you posted doesn't match with what you said.) After that you can use a something like for i in "$arg1" "$arg2"; do echo "$i"; done to check the two args that are passed to scp. (Just type them in the exact same way you type them after scp -pr.) – Tom Yan – 2019-09-24T15:16:34.137

If you can't even provide a sample of the check result with only the "secret wordings" replaced and can't tell what's not right in the args yourself, there's nothing we can do. – Tom Yan – 2019-09-24T15:20:37.973

Answers

1

You can replace the first set of spaces with a tab and set the IFS to "\t" and read both id and name with read, something like this:

IFS="\t" cat test | sed -e '1d' -e 's/ \[.*//g' -e "s/\s\+/\t/" |\
    while read id name; do echo -e "id:$id\nname:$name";done
id:1
name:VM_1
id:10
name:VM_2 with_space
id:11
name:VM_3 with space
id:12
name:VM_4 with_space
id:14
name:VM_5

However, from the output you have from getallvms, i don't think that's safe. The output seems to be fixed width, but it's not clear if that width is the same or it depends on the length of the VM name, or if the name is truncated if it's too long. I've searched for a manual for vim-cmd to see if you can control the format but there is no documentation online that covers command line options. Check if vim-cmd has any options for controlling the output (maybe you can set a explicit separator for the columns, or it can export xml/json ...)

If that's not possible, my suggestion is to get only the vmid from getallvms and use vim-cmd vmsvc/get.summary $vmid to get the name.

I would need a sample of the get.summary output to give you the exact code, but mainly it would look like this:

vim-cmd vmsvc/getallvms | sed -e '1d' | awk "{print $1} | while read id; do 
    vmname=$(vim-cmd vmsvc/get.summary $vmid | \
        sed '/ guest =/,/}/d' | egrep name | sed 's/.*= //;s/,//;s/ *$//') 
...
done

This is not tested. Most of it I got from: http://unixetc.co.uk/2015/03/28/list-virtual-machines-on-esxi/

Ah, now I see your problem is actually the SCP command, remote scp addresses need to be double escaped. Try:

scp -pr "/vmfs/volumes/datastore1/$vmname/$vmname-flat.vmdk" $addrs:"'/vmfs/volumes/datastore1/$vmname'"

Or if you have bash, you can use printf to quote the filename:

SRC=$(printf /vmfs/volumes/datastore1/%q/%q-flat.vmdk "$VMNAME" "$VMNAME")
DEST=$(printf %s:'/vmfs/volumes/datastore1/%q' $addr "$VMNAME")

and use that in your scp commands

Sorin

Posted 2019-09-24T12:25:39.750

Reputation: 137

million thanx !! Your advice with IFS and final advice with double quoting did the job !! even more, my script went thin by more than 60% to only 8 lines!!! did all in one loop, only one sed, in a single line i got everything i needed for the backup !! – Costin – 2019-09-25T07:52:59.710

0

Maybe it's not relevant, but using perl, it's quite easy to achieve.

Given test.out:

Vmid   Name            File                                             Guest OS               Version       Annotation
1      VM_1            [datastore1] VM_1/VM_1.vmx                       winXPProGuest           vmx-13
10     VM_2 with_space [datastore1] VM_2 with_space/VM_2 with_space.vmx opensuse64Guest         vmx-13
11     VM_3 with space [datastore1] VM_3 with space/VM_3 with space.vmx opensuse64Guest         vmx-13
12     VM_4 with_space [datastore1] VM_4 with_space/VM_4 with_space.vmx opensuse64Guest         vmx-13
14     VM_5            [datastore1] VM_5/VM_5.vmx                       winXPProGuest           vmx-13

The job is done using the following command line:

$ cat test.out |perl -ne 'if (/(\d+)[ \t]*([^[]+)/) { print "id is $1, name is $2\n"; }'
id is 1, name is VM_1            
id is 10, name is VM_2 with_space 
id is 11, name is VM_3 with space 
id is 12, name is VM_4 with_space 
id is 14, name is VM_5

You can easily adapt perl's output to make it more usable with shell...

NOTE: This oneliner memorize the name until the string "[datastore]" is met... if file column differs from "[datastore]", script is broken ...

binarym

Posted 2019-09-24T12:25:39.750

Reputation: 320

thank you, but it's irelevant. It's even simplier in python, but VMware ESXi hosts have only busybox and you can't install anything except their own packages. I can't even use bash. – Costin – 2019-09-25T07:48:10.730