Как разбить строку с кавычками (например, аргументы команды) в 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 рассматривает строку в двойных кавычках как один токен и игнорирует пробел между