Как разбить строку с кавычками (например, аргументы команды) в Bash?

У меня есть такая строка:

"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"

Я хочу иметь возможность разделить это так:

aString that may haveSpaces IN IT
bar
foo
bamboo  
bam boo

Как я могу это сделать? (желательно с использованием одной строки)

7 ответов

Решение

Когда я увидел ответ Дэвида Постила, я подумал: "Должно быть более простое решение". После некоторых экспериментов я нашел следующие работы:

string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"'
echo $string
eval 'for word in '$string'; do echo $word; done'

Это работает, потому что eval расширяет строку (удаляя кавычки и расширяя string) перед выполнением результирующей строки (которая является встроенным ответом):

for word in "aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"; do echo $word; done

Альтернатива, которая расширяется до той же строки:

eval "for word in $string; do echo \$word; done"

Вот string раскрывается в двойных кавычках, но $ нужно сбежать так, чтобы word не раскрывается до выполнения строки (в другой форме использование одинарных кавычек имеет тот же эффект). Результаты:

[~/]$ string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"'
[~/]$ echo $string
"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"
[~/]$ eval 'for word in '$string'; do echo $word; done'
aString that may haveSpaces IN IT
bar
foo
bamboo
bam boo
[~/]$ eval "for word in $string; do echo \$word; done"
aString that may haveSpaces IN IT
bar
foo
bamboo
bam boo

Самое простое решение - использование массива аргументов в кавычках, который вы можете затем зациклить, если хотите, или напрямую передать команде.

eval "array=($string)"

for arg in "${array[@]}"; do echo "$arg"; done   

Пожалуйста, прокомментируйте, если вы найдете более простой способ без eval,

Редактировать:

Основываясь на ответе @Hubbitus, мы имеем полностью очищенную и правильно процитированную версию. Примечание: это избыточно и фактически оставит дополнительные обратные слэши в разделах с двойными или одинарными кавычками, предшествующими большинству знаков препинания, но неуязвимо для атаки.

declare -a "array=($( echo "$string" | sed 's/[][`~!@#$%^&*():;<>.,?/\|{}=+-]/\\&/g' ))"

Я оставляю заинтересованным читателям возможность изменять по своему усмотрению http://ideone.com/FUTHhj

Похоже, что xargs может сделать это довольно хорошо:

      $ a='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"'
$ printf "%s" "$a" | xargs -n 1 printf "%s\n"
aString that may haveSpaces IN IT
bar
foo
bamboo
bam boo

Как я могу это сделать?

$ for l in "aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"; do echo $l; done
aString that may haveSpaces IN IT
bar
foo
bamboo
bam boo

Что мне делать, если моя строка находится в bash переменная?

Простой подход использования bash Строка Tokenizer не будет работать, так как она разделяется на все пробелы, а не только на те, которые находятся вне кавычек:

DavidPostill@Hal /f/test
$ cat ./test.sh
#! /bin/bash
string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"'
for word in $string; do echo "$word"; done

DavidPostill@Hal /f/test
$ ./test.sh
"aString
that
may
haveSpaces
IN
IT"
bar
foo
"bamboo"
"bam
boo"

Чтобы обойти это, следующий сценарий оболочки (splitstring.sh) показывает один подход:

#! /bin/bash 
string=$(cat <<'EOF'
"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo" 
EOF
)
echo Source String: "$string"
results=()
result=''
inside=''
for (( i=0 ; i<${#string} ; i++ )) ; do
    char=${string:i:1}
    if [[ $inside ]] ; then
        if [[ $char == \\ ]] ; then
            if [[ $inside=='"' && ${string:i+1:1} == '"' ]] ; then
                let i++
                char=$inside
            fi
        elif [[ $char == $inside ]] ; then
            inside=''
        fi
    else
        if [[ $char == ["'"'"'] ]] ; then
            inside=$char
        elif [[ $char == ' ' ]] ; then
            char=''
            results+=("$result")
            result=''
        fi
    fi
    result+=$char
done
if [[ $inside ]] ; then
    echo Error parsing "$result"
    exit 1
fi

echo "Output strings:"
for r in "${results[@]}" ; do
    echo "$r" | sed "s/\"//g"
done

Выход:

$ ./splitstring.sh
Source String: "aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"
Output strings:
aString that may haveSpaces IN IT
bar
foo
bamboo
bam boo

Источник: StackOverflow answer Разбить строку только по пробелам, которые находятся вне кавычек Чороба. Скрипт был изменен в соответствии с требованиями вопроса.

Вы можете сделать это с declare вместо eval, например:

Вместо:

string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"'
echo "Initial string: $string"
eval 'for word in '$string'; do echo $word; done'

Делать:

declare -a "array=($string)"
for item in "${array[@]}"; do echo "[$item]"; done

Но обратите внимание, это не намного безопаснее, если ввод поступает от пользователя!

Итак, если вы попробуете это, скажем, строка вроде:

string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo" `hostname`'

Ты получаешь hostname оценил (там, конечно, может быть что-то вроде rm -rf /)!

Очень-очень простая попытка защитить его, просто заменить символы, такие как backtrick `и $:

string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo" `hostname`'
declare -a "array=( $(echo $string | tr '`$<>' '????') )"
for item in "${array[@]}"; do echo "[$item]"; done

Теперь вы получили вывод, как:

[aString that may haveSpaces IN IT]
[bar]
[foo]
[bamboo]
[bam boo]
[?hostname?]

Более подробную информацию о методах, плюсах и минусах вы найдете в этом хорошем ответе: https://stackoverflow.com/questions/17529220/why-should-eval-be-avoided-in-bash-and-what-should-i-use-instead/17529221

Но там все же оставлен вектор для атаки.Я очень хочу иметь в bash метод строковых кавычек, как в двойных кавычках ("), но без интерпретации содержимого.

Развернув ответ Оливера , используя xargs и объявив, что список можно преобразовать в выражение присваивания, безопасное для оценки.

      echo "1 2 '3 4' 5" |  xargs bash -c 'declare -a array=("$@"); declare -p array' --

Использовать awk

echo '"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"' | awk 'BEGIN {FPAT = "([^ ]+)|(\"[^\"]+\")"}{for(i=1;i<=NF;i++){gsub("\"","",$i);print $i} }'
aString that may haveSpaces IN IT
bar
foo
bamboo
bam boo

Или преобразуйте пробел в "%20" или "_", чтобы его можно было обработать следующей командой throw pip:

echo '"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"' | awk 'BEGIN {FPAT = "([^ ]+)|(\"[^\"]+\")"}{for(i=1;i<=NF;i++){gsub("\"","",$i);gsub(" ","_",$i)} print }'
aString_that_may_haveSpaces_IN_IT bar foo bamboo bam_boo

ссылка: Awk рассматривает строку в двойных кавычках как один токен и игнорирует пробел между

Другие вопросы по тегам