В Вашем первом коде ssh
“украдет” STDIN из while
. Добавить -n
опция к ssh
избегать его. От man ssh
:
-n Redirects stdin from /dev/null (actually, prevents reading from stdin).
Для удаления нескольких (непространство) последовательные символы разделителя два (строка/массив), расширения параметра могут использоваться. Прием должен установить IFS
переменная к пустой строке для расширения параметра массива.
Это документируется в man bash
в соответствии с Word Splitting:
Удалены неупомянутые неявные нулевые аргументы, следуя из расширения параметров, которые не имеют никаких значений.
(
set -f
str=':abc::def:::ghi::::'
IFS=':'
arr=(${str})
IFS=""
arr=(${arr[@]})
echo ${!arr[*]}
for ((i=0; i < ${#arr[@]}; i++)); do
echo "${i}: '${arr[${i}]}'"
done
)
От bash
страница справочника:
Любой символ в IFS, которая не является пробелом IFS, наряду ни с какими смежными пробельными символами IFS, разграничивает поле. Последовательность пробельных символов IFS также рассматривают как разделитель.
Это означает, что пробел IFS (пространство, вкладка и новая строка) не рассматривают как другие разделители. Если Вы хотите получить точно то же поведение с альтернативным разделителем, можно сделать некоторый свопинг разделителя с помощью tr
или sed
:
var=":abc::def:::ghi::::"
arr=($(echo -n $var | sed 's/ /%#%#%#%#%/g;s/:/ /g'))
for x in ${!arr[*]} ; do
el=$(echo -n $arr | sed 's/%#%#%#%#%/ /g')
echo "# arr[$x] \"$el\""
done
%#%#%#%#%
вещью является волшебное значение для замены возможных пробелов в полях, она, как ожидают, будет "уникальна" (или очень unlinkely). Если Вы уверены, что никакое пространство никогда не будет в полях, просто отбросьте эту часть).
tr
примеры для показа проблемы... Я хочу избежать системного вызова, таким образом, я посмотрю на опцию удара вне ${var##:}
который я упомянул в своем комментарии ansewer долины реки..... Я буду ожидать какое-то время.. возможно, существует способ подключить IFS коаксиальным кабелем, иначе первая часть Вашего ответа, был после.... работы
– Peter.O
23.02.2011, 19:31
IFS
то же во всех оболочках стиля Границы, оно указано в POSIX.
– Gilles 'SO- stop being evil'
23.02.2011, 23:25
IFS
символы как строка разделителя. Моим вопросом лучше всего ответили jon_d
, но ответ @nazad's показывает изящный способ использовать IFS
без циклов и никаких служебных приложений.
– Peter.O
07.05.2015, 05:48
Как колотят IFS, не обеспечивает внутренний способ рассматривать последовательные символы разделителя как единственный разделитель (для непробельных разделителей), я соединил всю версию удара (vs.using внешний вызов, например, TR, awk, sed)
Это может обработать mult-символьную IFS..
Вот его время выполнения resu; ts, наряду с подобными тестами для tr
и awk
варианты показываются на этой странице Q/A... Тесты основаны на 10 000 повторений просто создания массива (без ввода-вывода)...
pure bash 3.174s (28 char IFS)
call (awk) 0m32.210s (1 char IFS)
call (tr) 0m32.178s (1 char IFS)
Вот вывод
# dlm_str = :.~!@#$%^&()_+-=`}{][ ";></,
# original = :abc:.. def:.~!@#$%^&()_+-=`}{][ ";></,'single*quote?'..123:
# unified = :abc::::def::::::::::::::::::::::::::::'single*quote?'::123:
# max-w 2^ = ::::::::::::::::
# shrunk.. = :abc:def:'single*quote?':123:
# arr[0] "abc"
# arr[1] "def"
# arr[2] "'single*quote?'"
# arr[3] "123"
Вот сценарий
#!/bin/bash
# Note: This script modifies the source string.
# so work with a copy, if you need the original.
# also: Use the name varG (Global) it's required by 'shrink_repeat_chars'
#
# NOTE: * asterisk in IFS causes a regex(?) issue, but * is ok in data.
# NOTE: ? Question-mark in IFS causes a regex(?) issue, but ? is ok in data.
# NOTE: 0..9 digits in IFS causes empty/wacky elements, but they're ok in data.
# NOTE: ' single quote in IFS; don't know yet, but ' is ok in data.
#
function shrink_repeat_chars () # A 'tr -s' analog
{
# Shrink repeating occurrences of char
#
# $1: A string of delimiters which when consecutively repeated and are
# considered as a shrinkable group. A example is: " " whitespace delimiter.
#
# $varG A global var which contains the string to be "shrunk".
#
# echo "# dlm_str = $1"
# echo "# original = $varG"
dlms="$1" # arg delimiter string
dlm1=${dlms:0:1} # 1st delimiter char
dlmw=$dlm1 # work delimiter
# More than one delimiter char
# ============================
# When a delimiter contains more than one char.. ie (different byte` values),
# make all delimiter-chars in string $varG the same as the 1st delimiter char.
ix=1;xx=${#dlms};
while ((ix<xx)) ; do # Where more than one delim char, make all the same in varG
varG="${varG//${dlms:$ix:1}/$dlm1}"
ix=$((ix+1))
done
# echo "# unified = $varG"
#
# Binary shrink
# =============
# Find the longest required "power of 2' group needed for a binary shrink
while [[ "$varG" =~ .*$dlmw$dlmw.* ]] ; do dlmw=$dlmw$dlmw; done # double its length
# echo "# max-w 2^ = $dlmw"
#
# Shrik groups of delims to a single char
while [[ ! "$dlmw" == "$dlm1" ]] ; do
varG=${varG//${dlmw}$dlm1/$dlm1}
dlmw=${dlmw:$((${#dlmw}/2))}
done
varG=${varG//${dlmw}$dlm1/$dlm1}
# echo "# shrunk.. = $varG"
}
# Main
varG=':abc:.. def:.~!@#$%^&()_+-=`}{][ ";></,'\''single*quote?'\''..123:'
sfi="$IFS"; IFS=':.~!@#$%^&()_+-=`}{][ ";></,' # save original IFS and set new multi-char IFS
set -f # disable globbing
shrink_repeat_chars "$IFS" # The source string name must be $varG
arr=(${varG:1}) # Strip leading dlim; A single trailing dlim is ok (strangely
for ix in ${!arr[*]} ; do # Dump the array
echo "# arr[$ix] \"${arr[ix]}\""
done
set +f # re-enable globbing
IFS="$sfi" # re-instate the original IFS
#
exit
Можно сделать это с простофилей также, но это не симпатично:
var=":abc::def:::ghi::::"
out=$( gawk -F ':+' '
{
# strip delimiters from the ends of the line
sub("^"FS,"")
sub(FS"$","")
# then output in a bash-friendly format
for (i=1;i<=NF;i++) printf("\"%s\" ", $i)
print ""
}
' <<< "$var" )
eval arr=($out)
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
выводы
# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"
$var
кому: ${var##:}
... Я был действительно после способа настроить саму IFS.. Я хочу сделать это без внешнего вызова (у меня есть чувство, что удар может сделать это более эффективно, чем какая-либо внешняя банка.. таким образом, я сохраню ту дорожку)... Ваши работы метода (+1).... До изменения входа идет, я предпочел бы пробовать его ударом, а не awk или TR (это избежит системного вызова), но я действительно болтаюсь для тонкой настройки IFS...
– Peter.O
23.02.2011, 19:57
bash 1.276s
... call (awk) 0m32.210s
,,, call (tr) 0m32.178s
... Сделайте это несколько раз, и Вы могли бы думать, что удар является медленным!... Действительно ли awk легче в этом случае?... не, если Вы уже получили отрывок :)... Я отправлю его позже; должен пойти теперь.
– Peter.O
24.02.2011, 05:00
var="The \"X\" factor:::A single '\"' crashes:::\"One Two\""
– Peter.O
24.02.2011, 18:28
Простой ответ: :свернуть все разделители в один (первый ).
Для этого требуется цикл (, который выполняется менее log(N)
раз):
var=':a bc::d ef:#$%_+$$% ^%&*(*&*^
$#,.::ghi::*::' # a long test string.
d=':@!#$%^&*()_+,.' # delimiter set
f=${d:0:1} # first delimiter
v=${var//["$d"]/"$f"}; # convert all delimiters to
: # the first of the delimiter set.
tmp=$v # temporal variable (v).
while
tmp=${tmp//["$f"]["$f"]/"$f"}; # collapse each two delimiters to one
[[ "$tmp" != "$v" ]]; # If there was a change
do
v=$tmp; # actualize the value of the string.
done
Осталось правильно разделить строку на один разделитель и напечатать:
readarray -td "$f" arr < <(printf '%s%s' "$v"'' "$f")
printf '<%s>' "${arr[@]}" ; echo
Нет необходимости ни в set -f
, ни в изменении IFS.
Протестировано с пробелами, переводами строк и символами глобуса. Все работает. Довольно медленный (, поскольку следует ожидать, что цикл оболочки будет ).
Но только для bash (bash 4.4+ из-за опции -d
для чтения массива ).
Версия оболочки не может использовать массив, единственным доступным массивом являются позиционные параметры.
Использование tr -s
— это всего лишь одна строка (IFS не изменяется в сценарии):
set -f; IFS=$f command eval set -- '$(echo "$var" | tr -s "$d" "[$f*]" )""'
И распечатать:
printf '<%s>' "$@" ; echo
Все еще медленно, но не более того.
Команда command
недействительна в Борне.
в зш,command
вызывает только внешние команды и приводит к сбою eval, если используется command
.
В ksh даже с command
значение IFS изменяется в глобальной области видимости.
И command
приводит к сбою разделения в оболочках, связанных с mksh (mksh, lksh, posh )Удаление команды command
приводит к запуску кода на большем количестве оболочек. Но :удаление command
заставит IFS сохранить свое значение в большинстве оболочек (eval — это специальная встроенная функция ), за исключением bash (без режима posix )и zsh по умолчанию (без режима эмуляции ). Эта концепция не может работать в zsh по умолчанию ни с command
, ни без нее.
Да, IFS может быть многосимвольным, но каждый символ будет генерировать один аргумент:
set -f; IFS="$d" command eval set -- '$(echo "$var" )""'
printf '<%s>' "$@" ; echo
Будет вывод:
<><a bc><><d ef><><><><><><><><>< ><><><><><><><><><
><><><><><><ghi><><><><><>
В bash вы можете опустить слово command
, если оно не используется в эмуляции sh/POSIX. Команда завершится ошибкой в ksh93 (IFS сохранит измененное значение ). В zsh команда command
заставляет zsh попытаться найти eval
как внешнюю команду (, которую он не находит )и терпит неудачу.
Происходит следующее: единственными символами IFS, которые автоматически сворачиваются в один разделитель, являются пробелы IFS.
Один пробел в IFS свернет все последовательные пробелы в один. Одна вкладка свернет все вкладки. Один пробел и одна табуляция будут сворачивать наборы пробелов и/или табуляции в один разделитель. Повторите идею с новой строкой.
Чтобы свернуть несколько разделителей, нужно немного повозиться.
Предполагая, что ASCII 3 (0x03 )не используется во вводеvar
:
var=${var// /$'\3'} # protect spaces
var=${var//["$d"]/ } # convert all delimiters to spaces
set -f; # avoid expanding globs.
IFS=" " command eval set -- '""$var""' # split on spaces.
set -- "${@//$'\3'/ }" # convert spaces back.
Большинство комментариев о ksh, zsh и bash (, о command
и IFS )применимы и здесь.
Значение $'\0'
было бы менее вероятным при текстовом вводе, но переменные bash не могут содержать NUL(0x00
).
В sh нет внутренних команд для выполнения тех же строковых операций, поэтому tr — единственное решение для сценариев sh.
IFS=' '
(т.е. пробел), ведет себя то же. Я нахожу это менее сбивающим с толку, чем явный нулевой аргумент ("" или'')IFS
. – Micha Wiedenmann 22.09.2015, 18:13