文字列を2つに分割し、各セグメントを変数に割り当てる方法

文字列を2つに分割し、各セグメントを変数に割り当てる方法

次のようなコードがあります:

#!/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

最初の部分 (設定ファイルのバックアップ) は問題なく動作します。2 番目の部分 (VM ディスクのバックアップ) は動作しません。ループを開始する vim-cmd 行の出力は次のとおりです。

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

する必要がある:

  • 最初の単語(数値ID)を変数$vmidに代入する
  • 残りの単語を var $vmname に割り当てて、scp コマンドのパスを連結し、ディレクトリ名とファイル名のスペースによって生成される問題にぶつかることなく正常に実行できるようにします。

現時点では、名前にスペースのないすべての VM で成功します。その他の VM では次のようになります:

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:

更新 1: 「vim-cmd vmsvc/getallvm s」の出力を知りたい人のために、以下を示します。

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

更新2:

@Sorin のおかげで、このモンスターを 60% 以上削減して 8 行にできました。これが、無料の ESXi 6.5 ホストを別のホストにクローンする貧乏人のソリューションです。

#!/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

ありがとう !!

答え1

最初のスペースのセットをタブに置き換え、IFS を "\t" に設定し、read を使用して ID と名前の両方を読み取ることができます。次のようになります。

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

ただし、getallvms からの出力から判断すると、安全ではないと思います。出力は固定幅のようですが、その幅が同じか、VM 名の長さによって異なるか、または名前が長すぎる場合は切り捨てられるかは不明です。形式を制御できるかどうかを確認するために vim-cmd のマニュアルを検索しましたが、コマンドライン オプションについて説明しているオンライン ドキュメントはありません。vim-cmd に出力を制御するオプションがあるかどうかを確認してください (列に明示的な区切りを設定したり、xml/json をエクスポートしたりできます)。

それが不可能な場合は、getallvms から vmid のみを取得し、それを使用してvim-cmd vmsvc/get.summary $vmid名前を取得することをお勧めします。

正確なコードを提供するには get.summary 出力のサンプルが必要ですが、基本的には次のようになります。

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

これはテストされていません。ほとんどは以下から取得しました:http://unixetc.co.uk/2015/03/28/list-virtual-machines-on-esxi/

ああ、問題は実際には SCP コマンドにあることがわかりました。リモート SCP アドレスは二重にエスケープする必要があります。次を試してください:

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

または、bash をお持ちの場合は、printf を使用してファイル名を引用符で囲むことができます。

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

それをscpコマンドで使用します

答え2

関係ないかもしれませんが、perl を使用すると、非常に簡単に実現できます。

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

このジョブは次のコマンド ラインを使用して実行されます。

$ 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

perl の出力を簡単に調整して、シェルでより使いやすくすることができます...

注記: このワンライナーは、文字列 "[datastore]" に出会うまで名前を記憶します...ファイル列が "[datastore]" と異なる場合、スクリプトは壊れます...

関連情報