Может IFS (Внутренний Разделитель полей) функция как единственный разделитель для нескольких последовательных символов разделителя?

В Вашем первом коде ssh “украдет” STDIN из while. Добавить -n опция к ssh избегать его. От man ssh:

-n     Redirects stdin from /dev/null (actually, prevents reading from stdin).
10
08.01.2019, 10:05
5 ответов

Для удаления нескольких (непространство) последовательные символы разделителя два (строка/массив), расширения параметра могут использоваться. Прием должен установить 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
)
3
27.01.2020, 20:02
  • 1
    Хороший! Простой и эффективный метод - без потребности в цикле удара и никакой потребности назвать служебное приложение — BTW. Как Вы упомянули" (не располагают с интервалами)", указал бы я для ясности, что она хорошо работает с любой комбинацией символов разделителя, включая пространство. –  Peter.O 22.05.2015, 21:37
  • 2
    В моей тестовой установке IFS=' ' (т.е. пробел), ведет себя то же. Я нахожу это менее сбивающим с толку, чем явный нулевой аргумент ("" или'') IFS. –  Micha Wiedenmann 22.09.2015, 18:13
  • 3
    Это - своего рода ужасное решение, если Ваши данные содержат встроенный пробел. Это, если бы Ваши данные были 'до н.э' вместо 'abc', IFS = "", разделило бы на отдельный элемент от 'до н.э' прекрасной идеи –  Dejay Clayton 24.09.2015, 18:19

От 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). Если Вы уверены, что никакое пространство никогда не будет в полях, просто отбросьте эту часть).

5
27.01.2020, 20:02
  • 1
    @FussyS... Спасибо (см. модификацию в моем вопросе)... Вы, возможно, дали мне ответ на мой намеченный вопрос.. и тот ответ может быть (вероятно) "Нет никакого способа заставить IFS вести себя таким образом, я хочу"... Я intendet tr примеры для показа проблемы... Я хочу избежать системного вызова, таким образом, я посмотрю на опцию удара вне ${var##:} который я упомянул в своем комментарии ansewer долины реки..... Я буду ожидать какое-то время.. возможно, существует способ подключить IFS коаксиальным кабелем, иначе первая часть Вашего ответа, был после.... работы –  Peter.O 23.02.2011, 19:31
  • 2
    Та обработка IFS то же во всех оболочках стиля Границы, оно указано в POSIX. –  Gilles 'SO- stop being evil' 23.02.2011, 23:25
  • 3
    4 - плюс годы, так как я задал этот вопрос - я нашел ответ @nazad (отправленным более чем год назад), чтобы быть самым простым способом манипулировать IFS для создания массива с любым числом и комбинацией 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
2
27.01.2020, 20:02
  • 1
    Большая работа, интересные +1! –  F. Hauri 10.02.2013, 16:43

Можно сделать это с простофилей также, но это не симпатично:

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"
1
27.01.2020, 20:02
  • 1
    Спасибо... Я, кажется, не был ясен в своем основном запросе (измененный вопрос)... Достаточно легко сделать это, просто изменив мой $var кому: ${var##:} ... Я был действительно после способа настроить саму IFS.. Я хочу сделать это без внешнего вызова (у меня есть чувство, что удар может сделать это более эффективно, чем какая-либо внешняя банка.. таким образом, я сохраню ту дорожку)... Ваши работы метода (+1).... До изменения входа идет, я предпочел бы пробовать его ударом, а не awk или TR (это избежит системного вызова), но я действительно болтаюсь для тонкой настройки IFS... –  Peter.O 23.02.2011, 19:57
  • 2
    @fred, как упомянуто, IFS только хлебает несколько последовательных разделителей для пробельного значения по умолчанию. Иначе, последовательные разделители приводит к посторонним пустым полям. Я ожидаю, что один или два внешних вызова чрезвычайно вряд ли повлияют на производительность любым реальным способом. –  glenn jackman 23.02.2011, 22:23
  • 3
    @glen.. (Вы сказали, что Ваш ответ не "симпатичен".. Я думаю, что это!:) Однако я соединил всю версию удара (по сравнению с внешним вызовом) и на основе 10 000 повторений просто создания массива (никакой ввод-вывод)... bash 1.276s ... call (awk) 0m32.210s ,,, call (tr) 0m32.178s ... Сделайте это несколько раз, и Вы могли бы думать, что удар является медленным!... Действительно ли awk легче в этом случае?... не, если Вы уже получили отрывок :)... Я отправлю его позже; должен пойти теперь. –  Peter.O 24.02.2011, 05:00
  • 4
    Просто между прочим, ре Ваш сценарий простофили... Я в основном не использовал awk прежде, таким образом, я смотрел на него (и другие) подробно... Я не могу выбрать, почему, но я упомяну проблему во всяком случае.. Когда дали заключенные в кавычки данные, это освобождает кавычки и разделяет в пробелах между кавычками.. и катастрофические отказы для нечетных чисел кавычек... Вот данные тестирования: 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

Да, 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.

-1
27.01.2020, 20:02

Теги

Похожие вопросы