Как разделить строку с разделителями табуляцией в bash-скрипте БЕЗ свертывания пробелов?
У меня есть строка $LINE
и я хочу $ITEMS
чтобы быть версией этого массива, разделите на отдельные вкладки и сохраните пробелы. Вот где я сейчас нахожусь:
IFS=$'\n' ITEMS=($(echo "$LINE" | tr "\t" "\n"))
Проблема здесь в том, что IFS
один или более, так что он поглощает новые строки, вкладки, что угодно. Я пробовал несколько других вещей, основанных на других вопросах, опубликованных здесь, но они предполагают, что во всех полях всегда будет значение, а не пустое. И тот, который, кажется, держит ключ, далеко от меня и работает на весь файл (я просто разделяю одну строку).
Здесь я предпочитаю решение чисто BASH.
5 ответов
IFS
только один или более, если символы являются пробелами. Непробельные символы - это одиночные разделители. Таким образом, простое решение, если есть какой-то непробельный символ, который, как вы уверены, отсутствует в вашей строке, состоит в том, чтобы преобразовать вкладки в этот символ и затем разделить его:
IFS=$'\2' read -ra ITEMS <<<"${LINE//$'\t'/$'\2'}"
К сожалению, такие предположения, как "не существует \2
во входных данных "как правило, происходит сбой в долгосрочной перспективе, где" в долгосрочной перспективе "переводится как" в самый неподходящий момент ". Поэтому вы можете сделать это в два этапа:
IFS=$'\2' read -ra TEMP < <(tr $'\t\2' $'\2\t' <<<"$LINE")
ITEMS=("${TEMP[@]//$'\t'/$'\2'}")
Одна возможность: вместо расщепления IFS
, использовать -d
возможность read
"строки" из строки, оканчивающиеся табуляцией Тем не менее, вы должны убедиться, что ваша строка также заканчивается вкладкой, иначе вы потеряете последний элемент.
items=()
while IFS='' read -r -d$'\t' x; do
items+=( "$x" )
done <<< $' foo \t bar\nbaz \t foobar\t'
printf "===%s===\n" "${items[@]}"
Обеспечение конечной вкладки без добавления дополнительного поля может быть выполнено с помощью
if [[ $str != *$'\t' ]]; then str+=$'\t'; fi
если необходимо.
Специальные символы IFS:
Words of the form $'string' are treated specially. The word expands to
string, with backslash-escaped characters replaced as specified by the
ANSI C standard. Backslash escape sequences, if present, are decoded
as follows:
\a alert (bell)
\b backspace
\e
\E an escape character
\f form feed
\n new line
\r carriage return
\t horizontal tab
\v vertical tab
\\ backslash
\' single quote
\" double quote
\? question mark
\nnn the eight-bit character whose value is the octal value
nnn (one to three digits)
\xHH the eight-bit character whose value is the hexadecimal
value HH (one or two hex digits)
\uHHHH the Unicode (ISO/IEC 10646) character whose value is the
hexadecimal value HHHH (one to four hex digits)
\UHHHHHHHH
the Unicode (ISO/IEC 10646) character whose value is the
hexadecimal value HHHHHHHH (one to eight hex digits)
\cx a control-x character
Расширенный результат заключен в одинарные кавычки, как если бы знак доллара отсутствовал.
Строка в двойных кавычках, перед которой стоит знак доллара ($"string"), приведет к переводу строки в соответствии с текущей локалью. Если текущим языковым стандартом является C или POSIX, знак доллара игнорируется. Если строка переведена и заменена, замена заменяется двойными кавычками.
line=$'zero\tone\ttwo'
IFS=$'\t' read -a arr <<< "${line}"
declare -p
Выход
declare -a arr='([0]="zero" [1]="one" [2]="two")'
Заметка. Это не касается новых строк в line
,
Чистое решение для bash, которое будет разделяться только на вкладки и сохранять новые строки и другие забавные символы, если таковые имеются:
IFS=$'\t' read -r -a arr -d '' < <(printf '%s' "$line")
Попытайся:
$ line=$'zero\tone with\nnewlines\ttwo\t three \n\t\tfive\n'
$ IFS=$'\t' read -r -a arr -d '' < <(printf '%s' "$line")
$ declare -p arr
declare -a arr='([0]="zero" [1]="one with
newlines" [2]="two" [3]=" three
" [4]="five
")'
Как видите, это работает безупречно: оно сохраняет все (пробелы, новые строки и т. Д.), Разделяется только на символы табуляции.
Есть один недостаток: он не обрабатывает "пустые поля": обратите внимание, есть две последовательные вкладки в line
; мы ожидаем получить пустое поле в arr
, но это не так.
Есть еще один менее очевидный недостаток: код возврата read
является 1
Технически, для Bash в этой команде есть сбой. Это абсолютно не проблема, если вы не используете set -e
или же set -E
, но это не рекомендуется в любом случае (так что вы не должны).
Если вы можете жить с этими двумя незначительными недостатками, это может быть идеальным решением.
Обратите внимание, что мы используем < <(printf '%s' "$line")
и не <<< "$line"
кормить read
, так как последний вставляет завершающий перевод строки.