0

Take the following bash example (I should mention that my real output comes from vboxmanage list vms)

TEST='"ubuntu server" foo bar'
echo $TEST
"ubuntu server" foo bar

How do I get the three values separated (in a loop or separate variable)? Every solution I've found relies on space delimiter and will break the first value.

8
  • the best way would be to avoid having to do that, if it would be possible to get the input in some other format, with the values actually separated. Where does the input string come from?
    – ilkkachu
    Commented Jul 9, 2021 at 10:40
  • This output is not under my control (vboxmanage list vms)
    – Marco
    Commented Jul 9, 2021 at 10:52
  • What is it from the output of vboxmanage list vms that you want to have? The names of the VMs in a list? To iterate over?
    – Kusalananda
    Commented Jul 9, 2021 at 11:03
  • 3
    Can you edit your post to add a verbatim example of the output you want to separate? Also what do you want to do with the individual tokens? The answer may depend on these factors.
    – AdminBee
    Commented Jul 9, 2021 at 11:04
  • 1
    Also note that a line of output from vmboxmanage list vms does not contain three words, it always seems to contain two words, a quoted virtual machine name and an UUID. It would be very nice if you could show real data. There will be two fields for each available VM.
    – Kusalananda
    Commented Jul 9, 2021 at 11:14

3 Answers 3

1

You may iterate over the names of the VirtualBox machines:

vboxmanage list -l vms | sed -n 's/^Name:[[:blank:]]*//p' |
while IFS= read -r vm_name; do
   printf 'There is one VM called "%s"\n' "$vm_name"
done

This lists the machines using the long format and parses out the names. The names are then read in a shell loop and a short message is printed for each.

You may also read the names into an array:

readarray -t vm_names < <(vboxmanage list -l vms | sed -n 's/^Name:[[:blank:]]*//p')
printf 'There is one VM called "%s"\n' "${vm_names[@]}"

Here, the list of names are red into the array vm_names. You may loop over this array like so:

for vm_name in "${vm_names[@]}"; do
   # Use "$vm_name" here
done

To also use the UUID:

vboxmanage list -l vms |
sed -n \
    -e '/^Name:[[:blank:]]*/{ s///; h; }' \
    -e '/^UUID:[[:blank:]]*/{ s///; G; y/\n/@/; p; }' |
while IFS=@ read -r vm_uuid vm_name; do
    printf 'VM "%s" has UUID "%s"\n' "$vm_name" "$vm_uuid"
done
7
  • This doesn't really help if what they have is just a string with the values and quotes embedded, e.g. from the output of some other command
    – ilkkachu
    Commented Jul 9, 2021 at 10:56
  • @ilkkachu I trust them to mention this in the question if that is the case.
    – Kusalananda
    Commented Jul 9, 2021 at 11:00
  • mm, it's right there in the title line, "how to split string" etc.
    – ilkkachu
    Commented Jul 9, 2021 at 11:01
  • @ilkkachu Note that the question contains a piece of code. In my answer, I'm suggesting changing that code.
    – Kusalananda
    Commented Jul 9, 2021 at 11:01
  • 1
    @Marco Yes you should. I have now fixed my initial answer. But it's still unclear what you data actually looks like as that command does not result in three words like you show.
    – Kusalananda
    Commented Jul 9, 2021 at 11:10
1

Assuming you have it just as a string, in the format shown in the code block in your initial question (i.e. "ubuntu server" foo bar), and the data is known to be sufficiently nice, i.e. only quoted strings (no shell special characters), you could use eval to evaluate it is as part of a shell command:

str='"ubuntu server" foo bar'
eval "arr=($str)"

With that input, this would give the array arr with the three elements ubuntu server, foo, and bar. You can use the array in the usual ways, e.g. "${arr[@]}" to expand to a list of words, one for each element.

But note that using eval involves evaluating the value for all shell syntax, not just quotes. E.g. if the string contains a command substitution $(cmd...), that command will run. If the string contains the unquoted );, it'll end the compound assignment statement, and what comes after will run as a shell command.

To be on the safe side, validate the input first, e.g. by checking it only contains quotes and safe characters. E.g. this would allow letters, numbers, blanks, the underscore and the two types of quotes, all of which should be safe here. The input could still have unmatching quotes, which would cause an error on the eval.

re='[^[:alnum:][:blank:]_'\''"]'
if [[ "$str" =~ $re ]]; then
    echo "unsafe input" >&2
else
    eval "arr=($str)";
    if [[ $? -ne 0 ]]; then
        echo "error in assignment" >&2
    fi
fi
3
  • Note that their data is not the output of the command that they mention. The output of that command looks like "some name" {19e47e6e-a030-4b25-9926-fc6258542fba} (one line of these per VM). It is likely that they want to iterate over the VMs.
    – Kusalananda
    Commented Jul 9, 2021 at 11:18
  • @Kusalananda, yes, that's why the command substitution was just in a comment, and why the "or whatever" was there. I wrote "Assuming you have it just as a string, in the format shown in your question" and they showed TEST='"ubuntu server" foo bar', so that's what I'm going with.
    – ilkkachu
    Commented Jul 9, 2021 at 11:22
  • It's not only expansions and globbing. If $str was defined as str=' "ubuntu server" ); reboot #' for instance, that would reboot. See also str=' [12]=var'. It evaluates the string as shell code, so can only be used to represent an array if $str contains a list of strings quoted in the syntax of the shell. Commented Dec 1, 2022 at 8:47
0

You can do word splitting with the set command. Here is a clumsy attempt but you may be able to improve on it.

First set the delimiter to double quote mark:

IFS='"'

then

set -- $TEST
echo $1

echo $2
ubuntu server
echo $3
 foo bar

You have the first (2-word) variable in $2 and the rest in $3

a=$2
b=$3

Now do another operation on variable b but this time with space delimiter

IFS=' '
set -- $b
echo $1
foo
echo $2
bar

This site is temporarily in read-only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .