Как обрезать пробелы из переменной Bash?


923

У меня есть сценарий оболочки с этим кодом:

var=`hg st -R "$path"`
if [ -n "$var" ]; then
    echo $var
fi

Но условный код всегда выполняется, потому что hg stвсегда печатает хотя бы один символ новой строки.

  • Есть ли простой способ убрать пробелы из $var(как trim()в PHP )?

или

  • Есть ли стандартный способ решения этой проблемы?

Я мог бы использовать sed или AWK , но я хотел бы думать, что есть более элегантное решение этой проблемы.


3
Связанные, если вы хотите обрезать пространство на целое число и просто получить целое число, оберните с $ (($ var)), и даже можете сделать это, когда внутри двойных кавычек. Это стало важным, когда я использовал оператор даты и с именами файлов.
Volomike

«Есть ли стандартный способ решения этой проблемы?» Да, используйте [[вместо [. $ var=$(echo) $ [ -n $var ]; echo $? #undesired test return 0 $ [[ -n $var ]]; echo $? 1
user.friendly

Если это поможет, по крайней мере, где я тестирую это на Ubuntu 16.04. Используя следующие матчи подрезать во всех отношениях: echo " This is a string of char " | xargs. Однако , если вы имеете апостроф в тексте вы можете сделать следующее: echo " This i's a string of char " | xargs -0. Обратите внимание, что я упоминаю последний из xargs (4.6.0)
Луис Альварадо,

Условие не выполняется из-за новой строки, поскольку обратные метки поглощают последнюю новую строку. Это ничего не будет печатать test=`echo`; if [ -n "$test" ]; then echo "Not empty"; fi, однако это будет test=`echo "a"`; if [ -n "$test" ]; then echo "Not empty"; fi- поэтому в конце должно быть больше, чем просто перевод строки.
Меки

A = "123 4 5 6"; B = echo $A | sed -r 's/( )+//g';
Брузиуз

Ответы:


1023

Давайте определим переменную, содержащую начальные, конечные и промежуточные пробелы:

FOO=' test test test '
echo -e "FOO='${FOO}'"
# > FOO=' test test test '
echo -e "length(FOO)==${#FOO}"
# > length(FOO)==16

Как удалить все пробелы (обозначено [:space:]в tr):

FOO=' test test test '
FOO_NO_WHITESPACE="$(echo -e "${FOO}" | tr -d '[:space:]')"
echo -e "FOO_NO_WHITESPACE='${FOO_NO_WHITESPACE}'"
# > FOO_NO_WHITESPACE='testtesttest'
echo -e "length(FOO_NO_WHITESPACE)==${#FOO_NO_WHITESPACE}"
# > length(FOO_NO_WHITESPACE)==12

Как удалить только первые пробелы:

FOO=' test test test '
FOO_NO_LEAD_SPACE="$(echo -e "${FOO}" | sed -e 's/^[[:space:]]*//')"
echo -e "FOO_NO_LEAD_SPACE='${FOO_NO_LEAD_SPACE}'"
# > FOO_NO_LEAD_SPACE='test test test '
echo -e "length(FOO_NO_LEAD_SPACE)==${#FOO_NO_LEAD_SPACE}"
# > length(FOO_NO_LEAD_SPACE)==15

Как удалить только конечные пробелы:

FOO=' test test test '
FOO_NO_TRAIL_SPACE="$(echo -e "${FOO}" | sed -e 's/[[:space:]]*$//')"
echo -e "FOO_NO_TRAIL_SPACE='${FOO_NO_TRAIL_SPACE}'"
# > FOO_NO_TRAIL_SPACE=' test test test'
echo -e "length(FOO_NO_TRAIL_SPACE)==${#FOO_NO_TRAIL_SPACE}"
# > length(FOO_NO_TRAIL_SPACE)==15

Как убрать как начальные, так и конечные пробелы - объедините sed:

FOO=' test test test '
FOO_NO_EXTERNAL_SPACE="$(echo -e "${FOO}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
echo -e "FOO_NO_EXTERNAL_SPACE='${FOO_NO_EXTERNAL_SPACE}'"
# > FOO_NO_EXTERNAL_SPACE='test test test'
echo -e "length(FOO_NO_EXTERNAL_SPACE)==${#FOO_NO_EXTERNAL_SPACE}"
# > length(FOO_NO_EXTERNAL_SPACE)==14

С другой стороны , если ваш Баш поддерживает его, вы можете заменить echo -e "${FOO}" | sed ...с sed ... <<<${FOO}, например , так (для конечных пробелов):

FOO_NO_TRAIL_SPACE="$(sed -e 's/[[:space:]]*$//' <<<${FOO})"

63
Обобщая решение для обработки всех форм пробелов, заменить символ пробела в trи sedкоманде с [[:space:]]. Обратите внимание, что этот sedподход будет работать только на однострочном вводе. Подходы, которые работают с многострочным вводом, а также используют встроенные функции bash, см. В ответах @bashfu и @GuruM. Обобщенная, встроенная версия решения @Nicholas Sushkin будет выглядеть так: trimmed=$([[ " test test test " =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]; echo -n "${BASH_REMATCH[1]}")
mklement0

7
Если вы делаете это часто, добавление alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'"к вашему ~/.profileпозволяет вам использовать echo $SOMEVAR | trimи cat somefile | trim.
instanceof

Я написал sedрешение , которое использует только одно выражение , а не два: sed -r 's/^\s*(\S+(\s+\S+)*)\s*$/\1/'. Он обрезает начальные и конечные пробелы и фиксирует любые разделенные пробелами последовательности непробельных символов в середине. Наслаждайтесь!
Виктор Заманян

@VictorZamanian Ваше решение не работает, если входные данные содержат только пробелы. Решения Sed с двумя шаблонами, предоставленные MattyV и instanceof, прекрасно работают с вводом только пробелов.
Торбен

@ Торбен Справедливая точка. Я полагаю, что одно выражение можно сделать условным, |чтобы сохранить его как одно, а не несколько выражений.
Виктор Заманян

967

Простой ответ:

echo "   lol  " | xargs

Xargs сделает обрезку для вас. Это одна команда / программа, без параметров, которая возвращает обрезанную строку, просто!

Примечание: это не удаляет все внутренние пробелы, поэтому "foo bar"остается неизменным; это НЕ становится "foobar". Тем не менее, несколько пробелов будут сжаты в единичные пробелы, поэтому "foo bar"станет "foo bar". Кроме того, он не удаляет символы конца строки.


27
Ницца. Это работает очень хорошо. Я решил xargs echoпередать это просто, чтобы быть многословным о том, что я делаю, но xargs сам по себе будет использовать echo по умолчанию.
Будет ли

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

22
Единственная проблема с xargs состоит в том, что он вводит новую строку, если вы хотите, чтобы новая строка была отключена, я бы порекомендовал sed 's/ *$//'в качестве альтернативы. Вы можете увидеть xargsновую строку следующим образом: echo -n "hey thiss " | xargs | hexdump вы заметите, 0a73что aэто новая строка. Если вы сделаете то же самое с sed: echo -n "hey thiss " | sed 's/ *$//' | hexdumpвы увидите 0073, нет новой строки.

8
Осторожный; это сломается, если строка в xargs содержит лишние пробелы между ними. Как «это один аргумент». xargs разделит на четыре.
Бос

64
Это плохо. 1. Это превратится a<space><space>bв a<space>b. 2. Еще больше: оно превратится a"b"c'd'eв abcde. 3. Еще больше: он не включится a"bи т. Д.
Саша

359

Существует решение, которое использует только встроенные модули Bash, называемые подстановочными знаками :

var="    abc    "
# remove leading whitespace characters
var="${var#"${var%%[![:space:]]*}"}"
# remove trailing whitespace characters
var="${var%"${var##*[![:space:]]}"}"   
printf '%s' "===$var==="

Вот то же самое, заключенное в функцию:

trim() {
    local var="$*"
    # remove leading whitespace characters
    var="${var#"${var%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    var="${var%"${var##*[![:space:]]}"}"   
    printf '%s' "$var"
}

Вы передаете строку для обрезки в кавычки. например:

trim "   abc   "

Приятной особенностью этого решения является то, что оно будет работать с любой POSIX-совместимой оболочкой.

Ссылка


18
Умная! Это мое любимое решение, так как оно использует встроенную функциональность bash. Спасибо за публикацию! @ Сан, это две вложенные строки. Например, s=" 1 2 3 "; echo \""${s%1 2 3 }"\"урезать все с конца, возвращая ведущий " ". Подбирая 1 2 3 с [![:space:]]*указывает, что «найти первый непробельный символ, а затем забить его и все после». Использование %%вместо %делает операцию отделки с конца жадной. Это вложено в не жадную обрезку с самого начала, так что, по сути, вы обрезаете " "с самого начала. Затем поменяйте местами%, # и * для конечных пробелов. Бам!
Марк Г.

2
Я не обнаружил никаких нежелательных побочных эффектов, и основной код работает и с другими POSIX-подобными оболочками. Однако в Solaris 10 он не работает /bin/sh(только с /usr/xpg4/bin/sh, но это не то, что будет использоваться с обычными sh-скриптами).
vinc17

9
Намного лучшее решение, чем использование sed, tr и т. Д., Так как это намного быстрее, избегая использования fork (). На Cygwin разница в скорости составляет порядки величины.
Джин Павловский

9
@San Сначала я был в тупике, потому что думал, что это были регулярные выражения. Они не. Скорее всего , это шаблон синтаксис ( gnu.org/software/bash/manual/html_node/Pattern-Matching.html , wiki.bash-hackers.org/syntax/pattern ) , используемый в Substring удаления ( tldp.org/LDP/abs /html/string-manipulation.html ). Так ${var%%[![:space:]]*}говорит «удалить из varсамой длинной подстроки, которая начинается с непробельного символа». Это означает, что у вас остались только первые пробелы, которые вы впоследствии удалите ${var#... Следующая строка (трейлинг) противоположна.
Охад Шнайдер

8
В подавляющем большинстве это идеальное решение. Ветвление один или несколько внешних процессов (например, awk, sed, tr, xargs) просто обрезки пробельного из одной строки в корне безумен - особенно , когда большинство оболочек ( в том числе Баша) уже обеспечивает встроенную строку munging объектов вне коробки.
Сесил Карри

81

В Bash есть функция, называемая расширением параметров , которая, помимо прочего, позволяет заменять строки на основе так называемых шаблонов (шаблоны напоминают регулярные выражения, но есть фундаментальные различия и ограничения). [Оригинальная строка flussence: у Bash есть регулярные выражения, но они хорошо спрятаны:]

Ниже показано, как удалить все пустое пространство (даже изнутри) из значения переменной.

$ var='abc def'
$ echo "$var"
abc def
# Note: flussence's original expression was "${var/ /}", which only replaced the *first* space char., wherever it appeared.
$ echo -n "${var//[[:space:]]/}"
abcdef

2
Вернее, он работает для пробелов в середине переменной, но не тогда, когда я пытаюсь закрепить его в конце.
Пол Томблин

Это помогает любому? Из man-страницы: «$ {параметр / шаблон / строка} [...] Если шаблон начинается с%, он должен совпадать в конце расширенного значения параметра.»

@ Неужели они не регулярные выражения, а что-то похожее?
Пол Томблин

3
Они регулярные выражения, просто странный диалект.

13
${var/ /}удаляет первый пробел ${var// /}удаляет все пробелы Нет способа обрезать только начальные и конечные пробелы только с помощью этой конструкции.
Жиль "ТАК ... перестать быть злым"

60

Чтобы удалить все пробелы в начале и конце строки (включая символы конца строки):

echo $variable | xargs echo -n

Это также удалит дубликаты пробелов:

echo "  this string has a lot       of spaces " | xargs echo -n

Производит: «в этой строке много пробелов»


5
В основном xargs удаляет все разделители из строки. По умолчанию он использует пробел в качестве разделителя (это можно изменить с помощью параметра -d).
ркачач

4
Это, безусловно, самое чистое (как короткое, так и читаемое) решение.
Potherca

Зачем тебе echo -nвообще? echo " my string " | xargsимеет такой же выход.
bfontaine

echo -n также удаляет конец строки
rkachach

55

Удалите один ведущий и один завершающий пробел

trim()
{
    local trimmed="$1"

    # Strip leading space.
    trimmed="${trimmed## }"
    # Strip trailing space.
    trimmed="${trimmed%% }"

    echo "$trimmed"
}

Например:

test1="$(trim " one leading")"
test2="$(trim "one trailing ")"
test3="$(trim " one leading and one trailing ")"
echo "'$test1', '$test2', '$test3'"

Вывод:

'one leading', 'one trailing', 'one leading and one trailing'

Убрать все ведущие и конечные пробелы

trim()
{
    local trimmed="$1"

    # Strip leading spaces.
    while [[ $trimmed == ' '* ]]; do
       trimmed="${trimmed## }"
    done
    # Strip trailing spaces.
    while [[ $trimmed == *' ' ]]; do
        trimmed="${trimmed%% }"
    done

    echo "$trimmed"
}

Например:

test4="$(trim "  two leading")"
test5="$(trim "two trailing  ")"
test6="$(trim "  two leading and two trailing  ")"
echo "'$test4', '$test5', '$test6'"

Вывод:

'two leading', 'two trailing', 'two leading and two trailing'

9
Это обрезает только 1 пробел. Таким образом, эхо приводит к'hello world ', 'foo bar', 'both sides '
Джо

@ Джо, я добавил лучший вариант.
wjandrea

42

Из раздела Bash Guide по глобализации

Использовать extglob в расширении параметра

 #Turn on extended globbing  
shopt -s extglob  
 #Trim leading and trailing whitespace from a variable  
x=${x##+([[:space:]])}; x=${x%%+([[:space:]])}  
 #Turn off extended globbing  
shopt -u extglob  

Вот та же функциональность, заключенная в функцию (ПРИМЕЧАНИЕ: необходимо заключить в кавычки входную строку, переданную функции):

trim() {
    # Determine if 'extglob' is currently on.
    local extglobWasOff=1
    shopt extglob >/dev/null && extglobWasOff=0 
    (( extglobWasOff )) && shopt -s extglob # Turn 'extglob' on, if currently turned off.
    # Trim leading and trailing whitespace
    local var=$1
    var=${var##+([[:space:]])}
    var=${var%%+([[:space:]])}
    (( extglobWasOff )) && shopt -u extglob # If 'extglob' was off before, turn it back off.
    echo -n "$var"  # Output trimmed string.
}

Применение:

string="   abc def ghi  ";
#need to quote input-string to preserve internal white-space if any
trimmed=$(trim "$string");  
echo "$trimmed";

Если мы изменим функцию для выполнения в подоболочке, нам не нужно беспокоиться о проверке текущей опции оболочки для extglob, мы можем просто установить ее, не затрагивая текущую оболочку. Это значительно упрощает функцию. Я также обновляю позиционные параметры «на месте», поэтому мне даже не нужна локальная переменная

trim() {
    shopt -s extglob
    set -- "${1##+([[:space:]])}"
    printf "%s" "${1%%+([[:space:]])}" 
}

так:

$ s=$'\t\n \r\tfoo  '
$ shopt -u extglob
$ shopt extglob
extglob         off
$ printf ">%q<\n" "$s" "$(trim "$s")"
>$'\t\n \r\tfoo  '<
>foo<
$ shopt extglob
extglob         off

2
как вы заметили, trim () удаляет только начальные и конечные пробелы.
ГуруМ

Как уже отмечал mkelement, вам нужно передать параметр функции в виде строки в кавычках, т.е. $ (trim "$ string") вместо $ (trim $ string). Я обновил код, чтобы показать правильное использование. Спасибо.
ГуруМ

Как бы мне не хотелось знать о параметрах оболочки, я не думаю, что конечный результат более элегантен, чем
простая

Обратите внимание, что (с достаточно недавней версией Bash?) Вы можете упростить механизм восстановления опции extglob, используя shopt -p: просто напишите local restore="$(shopt -p extglob)" ; shopt -s extglobв начале вашей функции и eval "$restore"в конце (за исключением того, что eval - это зло ...).
Maëlan

Отличное решение! Одно потенциальное улучшение: похоже, его [[:space:]]можно заменить пробелом: ${var##+( )}и ${var%%+( )}работать, и их легче читать.
DKroot

40

Вы можете обрезать просто с помощью echo:

foo=" qsdqsd qsdqs q qs   "

# Not trimmed
echo \'$foo\'

# Trim
foo=`echo $foo`

# Trimmed
echo \'$foo\'

Это объединяет несколько смежных пространств в одно.
Евгений Сергеев

7
Вы пробовали это, когда fooсодержит подстановочный знак? например, foo=" I * have a wild card"... сюрприз! Более того, это объединяет несколько смежных пространств в одно.
gniourf_gniourf

5
Это отличное решение, если вы: 1. не хотите пробелов на концах 2. хотите, чтобы между каждым словом был только один пробел 3. работаете с контролируемым вводом без подстановочных знаков. Это по сути превращает плохо отформатированный список в хороший.
musicin3d

Хорошее напоминание о подстановочных знаках @gniourf_gniourf +1. Все еще превосходное решение, Вамп. +1 тебе тоже.
Доктор Беко

25

Я всегда делал это с помощью sed

  var=`hg st -R "$path" | sed -e 's/  *$//'`

Если есть более элегантное решение, я надеюсь, что кто-нибудь разместит его.


Не могли бы вы объяснить синтаксис для sed?
farid99

2
Регулярное выражение соответствует всем конечным пробелам и заменяет его ничем.
Пол Томблин

4
Как насчет ведущих пробелов?
Цянь Чен

Это удаляет все конечные пробелы sed -e 's/\s*$//'. Объяснение: «s» означает поиск, «\ s» означает все пробелы, «*» означает ноль или много, «$» означает до конца строки, а «//» означает замену всех совпадений пустой строкой ,
Крейг,

В 's / * $ //', почему перед звездочкой стоит 2 пробела, а не один пробел? Это опечатка?
Brent212


24

С включенными расширенными функциями сопоставления с образцом в Bash ( shopt -s extglob) вы можете использовать это:

{trimmed##*( )}

удалить произвольное количество ведущих пробелов.


Потрясающе! Я думаю, что это самое легкое и элегантное решение.
dubiousjim

1
Смотрите пост @ GuruM ниже для аналогичного, но более общего решения, которое (а) имеет дело со всеми формами пустого пространства и (б) также обрабатывает конечные пробелы.
mklement0

@mkelement +1 за труд переписать мой фрагмент кода как функцию. Спасибо
Гуру

Также работает с OpenBSD по умолчанию / bin / ksh. /bin/sh -o posixтоже работает но я с подозрением
Клинт Пахл,

Здесь не волшебник Баш; что trimmed? Это встроенная вещь или переменная, которая обрезается?
Абхиджит Саркар

19
# Trim whitespace from both ends of specified parameter

trim () {
    read -rd '' $1 <<<"${!1}"
}

# Unit test for trim()

test_trim () {
    local foo="$1"
    trim foo
    test "$foo" = "$2"
}

test_trim hey hey &&
test_trim '  hey' hey &&
test_trim 'ho  ' ho &&
test_trim 'hey ho' 'hey ho' &&
test_trim '  hey  ho  ' 'hey  ho' &&
test_trim $'\n\n\t hey\n\t ho \t\n' $'hey\n\t ho' &&
test_trim $'\n' '' &&
test_trim '\n' '\n' &&
echo passed

2
Удивительно! Просто и эффективно! Понятно мое любимое решение. Спасибо!
xebeche

1
@CraigMcQueen это значение переменной, так как она readбудет хранить в переменной по ее имени $ 1 урезанную версию ее значения $ {! 1}
Aquarius Power

2
Параметр функции trim () является именем переменной: см. Вызов функции trim () внутри test_trim (). В рамках trim (), вызываемой из test_trim (), $ 1 расширяется до foo, а $ {! 1} расширяется до $ foo (то есть до текущего содержимого переменной foo). Поищите в руководстве по bash 'переменная косвенность'.
flabdablet

1
Как насчет этой небольшой модификации, чтобы поддержать обрезку нескольких переменных в одном вызове? trim() { while [[ $# -gt 0 ]]; do read -rd '' $1 <<<"${!1}"; shift; done; }
Джин Павловский

2
@ AquariusPower нет необходимости использовать echo в подоболочке для однострочной версии, просто read -rd '' str <<<"$str"подойдет.
flabdablet

12

Есть много ответов, но я все еще верю, что мой только что написанный сценарий стоит упомянуть, потому что:

  • он был успешно протестирован в оболочке bash / dash / busybox
  • это очень мало
  • он не зависит от внешних команд и не нуждается в форке (-> быстрое и низкое использование ресурсов)
  • работает как положено:
    • он удаляет все пробелы и табуляции от начала и до конца, но не более
    • важно: он ничего не удаляет из середины строки (многие другие ответы делают), даже переводы строки останутся
    • special: "$*"объединяет несколько аргументов, используя один пробел. если вы хотите обрезать и вывести только первый аргумент, используйте "$1"вместо
    • если нет проблем с сопоставлением шаблонов имен файлов и т. д.

Сценарий:

trim() {
  local s2 s="$*"
  until s2="${s#[[:space:]]}"; [ "$s2" = "$s" ]; do s="$s2"; done
  until s2="${s%[[:space:]]}"; [ "$s2" = "$s" ]; do s="$s2"; done
  echo "$s"
}

Применение:

mystring="   here     is
    something    "
mystring=$(trim "$mystring")
echo ">$mystring<"

Вывод:

>here     is
    something<

Бах в C это было бы проще реализовать!
Нильс

Конечно. К сожалению, это не C, и иногда вы хотите избежать вызова внешних инструментов
Даниэль Алдер,

Чтобы сделать код более читабельным и совместимым с копированием, вы можете изменить скобки на экранированные символы:[\ \t]
leondepeon

@leondepeon ты пробовал это? Я попробовал, когда написал, и попробовал снова, и ваше предложение не работает ни в одном из bash, dash, busybox
Даниэль Олдер

@DanielAlder Я сделал, но, как это уже 3 года назад, я не могу найти код, где я его использовал. Однако теперь я, вероятно, использовал [[:space:]]бы как в одном из других ответов: stackoverflow.com/a/3352015/3968618
leondepeon

11

Вы можете использовать старую школу tr. Например, это возвращает количество измененных файлов в репозитории git, пробелы удалены.

MYVAR=`git ls-files -m|wc -l|tr -d ' '`

1
Это не удаляет пробелы спереди и сзади - оно удаляет все пробелы из строки.
Ник

11

Это сработало для меня:

text="   trim my edges    "

trimmed=$text
trimmed=${trimmed##+( )} #Remove longest matching series of spaces from the front
trimmed=${trimmed%%+( )} #Remove longest matching series of spaces from the back

echo "<$trimmed>" #Adding angle braces just to make it easier to confirm that all spaces are removed

#Result
<trim my edges>

Чтобы поместить это в меньшее количество строк для того же результата:

text="    trim my edges    "
trimmed=${${text##+( )}%%+( )}

1
Не работал для меня Первый напечатал не обрезанную строку. Второй бросил плохую замену. Можете ли вы объяснить, что здесь происходит?
musicin3d

1
@ musicin3d: это сайт, которым я часто пользуюсь, который объясняет, как работает манипулирование переменными в поиске bash для ${var##Pattern}получения более подробной информации. Кроме того, этот сайт объясняет шаблоны bash . Таким образом, ##средства удаляют данный шаблон спереди, а %%средства удаляют данный шаблон сзади. +( )Часть представляет собой шаблон , и это означает «один или более вхождение в пространстве»
gMale

Забавно, это работало в подсказке, но не после переноса в файл сценария bash.
Доктор Беко

странно. Это одна и та же версия Bash в обоих случаях?
gMale

11
# Strip leading and trailing white space (new line inclusive).
trim(){
    [[ "$1" =~ [^[:space:]](.*[^[:space:]])? ]]
    printf "%s" "$BASH_REMATCH"
}

ИЛИ

# Strip leading white space (new line inclusive).
ltrim(){
    [[ "$1" =~ [^[:space:]].* ]]
    printf "%s" "$BASH_REMATCH"
}

# Strip trailing white space (new line inclusive).
rtrim(){
    [[ "$1" =~ .*[^[:space:]] ]]
    printf "%s" "$BASH_REMATCH"
}

# Strip leading and trailing white space (new line inclusive).
trim(){
    printf "%s" "$(rtrim "$(ltrim "$1")")"
}

ИЛИ

# Strip leading and trailing specified characters.  ex: str=$(trim "$str" $'\n a')
trim(){
    if [ "$2" ]; then
        trim_chrs="$2"
    else
        trim_chrs="[:space:]"
    fi

    [[ "$1" =~ ^["$trim_chrs"]*(.*[^"$trim_chrs"])["$trim_chrs"]*$ ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

ИЛИ

# Strip leading specified characters.  ex: str=$(ltrim "$str" $'\n a')
ltrim(){
    if [ "$2" ]; then
        trim_chrs="$2"
    else
        trim_chrs="[:space:]"
    fi

    [[ "$1" =~ ^["$trim_chrs"]*(.*[^"$trim_chrs"]) ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

# Strip trailing specified characters.  ex: str=$(rtrim "$str" $'\n a')
rtrim(){
    if [ "$2" ]; then
        trim_chrs="$2"
    else
        trim_chrs="[:space:]"
    fi

    [[ "$1" =~ ^(.*[^"$trim_chrs"])["$trim_chrs"]*$ ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

# Strip leading and trailing specified characters.  ex: str=$(trim "$str" $'\n a')
trim(){
    printf "%s" "$(rtrim "$(ltrim "$1" "$2")" "$2")"
}

ИЛИ

Опираясь на опыт Москита ...

# Strip leading and trailing white space (new line inclusive).
trim(){
    printf "%s" "`expr "$1" : "^[[:space:]]*\(.*[^[:space:]]\)[[:space:]]*$"`"
}

ИЛИ

# Strip leading white space (new line inclusive).
ltrim(){
    printf "%s" "`expr "$1" : "^[[:space:]]*\(.*[^[:space:]]\)"`"
}

# Strip trailing white space (new line inclusive).
rtrim(){
    printf "%s" "`expr "$1" : "^\(.*[^[:space:]]\)[[:space:]]*$"`"
}

# Strip leading and trailing white space (new line inclusive).
trim(){
    printf "%s" "$(rtrim "$(ltrim "$1")")"
}

8

Я видел сценарии, которые просто используют переменные для выполнения работы:

$ xyz=`echo -e 'foo \n bar'`
$ echo $xyz
foo bar

Пробелы автоматически объединяются и обрезаются. Нужно быть осторожным с метасимволами оболочки (потенциальный риск инъекции).

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

if [ -n "$var" ]; then

поскольку что-то вроде -o или другого содержимого в переменной может изменить ваши тестовые аргументы.


3
Это неупомянуто использование $xyzс , echoчто делает пробельную коалесцирующее, не переменная назначению. Чтобы сохранить усеченное значение в переменной в вашем примере, вы должны будете использовать xyz=$(echo -n $xyz). Кроме того, этот подход подвержен потенциально нежелательному расширению пути (globbing).
mklement0

это просто неправильно, значение в xyzпеременной НЕ обрезается.
Цезарсоль

7
var='   a b c   '
trimmed=$(echo $var)

1
Это не сработает, если между двумя словами больше одного пробела. Попробуйте: echo $(echo "1 2 3")(с двумя пробелами между 1, 2 и 3).
joshlf

7

Я бы просто использовал sed:

function trim
{
    echo "$1" | sed -n '1h;1!H;${;g;s/^[ \t]*//g;s/[ \t]*$//g;p;}'
}

а) Пример использования однострочной строки

string='    wordA wordB  wordC   wordD    '
trimmed=$( trim "$string" )

echo "GIVEN STRING: |$string|"
echo "TRIMMED STRING: |$trimmed|"

Вывод:

GIVEN STRING: |    wordA wordB  wordC   wordD    |
TRIMMED STRING: |wordA wordB  wordC   wordD|

б) Пример использования многострочной строки

string='    wordA
   >wordB<
wordC    '
trimmed=$( trim "$string" )

echo -e "GIVEN STRING: |$string|\n"
echo "TRIMMED STRING: |$trimmed|"

Вывод:

GIVEN STRING: |    wordAA
   >wordB<
wordC    |

TRIMMED STRING: |wordAA
   >wordB<
wordC|

в) Последнее замечание:
если вам не нравится использовать функцию, для однострочной строки вы можете просто использовать команду «легче запомнить», например:

echo "$string" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//'

Пример:

echo "   wordA wordB wordC   " | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//'

Вывод:

wordA wordB wordC

Использование вышеперечисленного в многострочных строках также будет работать , но, пожалуйста, обратите внимание, что оно также сократит любой завершающий / ведущий внутренний множественный пробел, как заметил GuruM в комментариях.

string='    wordAA
    >four spaces before<
 >one space before<    '
echo "$string" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//'

Вывод:

wordAA
>four spaces before<
>one space before<

Поэтому, если вы не возражаете, оставьте эти пробелы, пожалуйста, используйте функцию в начале моего ответа!

d) ОБЪЯСНЕНИЕ синтаксиса sed «найти и заменить» в многострочных строках, используемых внутри функции trim:

sed -n '
# If the first line, copy the pattern to the hold buffer
1h
# If not the first line, then append the pattern to the hold buffer
1!H
# If the last line then ...
$ {
    # Copy from the hold to the pattern buffer
    g
    # Do the search and replace
    s/^[ \t]*//g
    s/[ \t]*$//g
    # print
    p
}'

Примечание: как предложено @mkelement, он не будет работать для многострочных строк, хотя он должен работать для однострочных строк.
ГуруМ

1
Вы не правы: он работает и на многострочных строках. Просто
проверь

+1 за использование - мне было легко протестировать код. Однако код по-прежнему не будет работать для многострочных строк. Если вы внимательно посмотрите на вывод, вы заметите, что все начальные / конечные внутренние пробелы также удаляются, например, пространство перед «многострочным» заменяется на «многострочное». Просто попробуйте увеличить количество пробелов в каждой строке.
ГуруМ

Теперь я понимаю, что вы имеете в виду! Спасибо за голову, я отредактировал мой ответ.
Лука Боррионе

@ "Luca Borrione" - добро пожаловать :-) Не могли бы вы объяснить синтаксис sed, который вы используете в trim ()? Это также может помочь любому пользователю вашего кода настроить его для других целей. Также это может даже помочь найти крайние случаи для регулярного выражения.
ГуруМ

6

Вот функция trim (), которая урезает и нормализует пробел

#!/bin/bash
function trim {
    echo $*
}

echo "'$(trim "  one   two    three  ")'"
# 'one two three'

И еще один вариант, который использует регулярные выражения.

#!/bin/bash
function trim {
    local trimmed="$@"
    if [[ "$trimmed" =~ " *([^ ].*[^ ]) *" ]]
    then 
        trimmed=${BASH_REMATCH[1]}
    fi
    echo "$trimmed"
}

echo "'$(trim "  one   two    three  ")'"
# 'one   two    three'

Первый подход сложен в том, что он не только нормализует внутренние пробелы (заменяет все внутренние промежутки пробелами на один пробел каждый), но также подвергается смещению (раскрытию пути), так что, например, *символ во входной строке будет разверните все файлы и папки в текущей рабочей папке. Наконец, если для $ IFS установлено значение не по умолчанию, обрезка может не работать (хотя это легко исправить, добавив local IFS=$' \t\n'). Обрезка ограничена следующими формами пробелов: пробелы \tи \nсимволы.
mklement0

1
Второй подход, основанный на регулярных выражениях, великолепен и не имеет побочных эффектов, но в его нынешнем виде проблематичен: (a) в bash v3.2 + сопоставление по умолчанию НЕ будет работать, поскольку регулярное выражение должно быть в кавычках, чтобы работать и (б) само регулярное выражение не обрабатывает случай, когда входная строка представляет собой один непробельный символ, окруженный пробелами. Чтобы устранить эти проблемы, замените ifстроку с: if [[ "$trimmed" =~ ' '*([^ ]|[^ ].*[^ ])' '* ]]. Наконец, подход касается только пробелов, а не других форм пробелов (см. Мой следующий комментарий).
mklement0

2
Функция, которая использует регулярные выражения, имеет дело только с пробелами, а не с другими формами пробелов, но ее легко обобщить: замените ifстроку на:[[ "$trimmed" =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]
mklement0

6

Используйте AWK:

echo $var | awk '{gsub(/^ +| +$/,"")}1'

Сладкий, который, кажется, работает (ex :) $stripped_version=echo $ var | awk '{gsub (/ ^ + | + $ /, "")} 1'``
rogerdpack

4
кроме того, что awk ничего не делает: эхо-переменная без
кавычек уже убрала

6

Назначения игнорируют начальные и конечные пробелы и как таковые могут быть использованы для обрезки:

$ var=`echo '   hello'`; echo $var
hello

8
Это не правда. Это «эхо», которое удаляет пробелы, а не назначение. В вашем примере сделайте, echo "$var"чтобы увидеть значение с пробелами.
Николай Сушкин

2
@NicholasSushkin Можно было бы сделать, var=$(echo $var)но я не рекомендую это. Другие решения, представленные здесь, являются предпочтительными.
xebeche

5

Это не имеет проблемы с нежелательным сглаживанием, также, внутреннее пустое пространство не изменяется (при условии, что $IFSустановлено значение по умолчанию, которое есть ' \t\n').

Он читает до первой новой строки (и не включает ее) или до конца строки, в зависимости от того, что наступит раньше, и удаляет любое сочетание начальных и конечных пробелов и \tсимволов. Если вы хотите сохранить несколько строк (а также убрать начальные и конечные новые строки), используйте read -r -d '' var << eofвместо этого; заметьте, однако, что, если ваш ввод будет содержать \neof, он будет отключен непосредственно перед. (Другие формы пробелов, а именно \r, \fи \v, не удаляются, даже если вы добавляете их в $ IFS.)

read -r var << eof
$var
eof


5

Это удалит все пробелы из вашей строки,

 VAR2="${VAR2//[[:space:]]/}"

/заменяет первое вхождение и //все вхождения пробелов в строке. Т.е. заменяются все пробелы - ничего


4

Это самый простой метод, который я видел. Он использует только Bash, это всего несколько строк, регулярное выражение простое и соответствует всем формам пробелов:

if [[ "$test" =~ ^[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$ ]]
then 
    test=${BASH_REMATCH[1]}
fi

Вот пример скрипта для тестирования:

test=$(echo -e "\n \t Spaces and tabs and newlines be gone! \t  \n ")

echo "Let's see if this works:"
echo
echo "----------"
echo -e "Testing:${test} :Tested"  # Ugh!
echo "----------"
echo
echo "Ugh!  Let's fix that..."

if [[ "$test" =~ ^[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$ ]]
then 
    test=${BASH_REMATCH[1]}
fi

echo
echo "----------"
echo -e "Testing:${test}:Tested"  # "Testing:Spaces and tabs and newlines be gone!"
echo "----------"
echo
echo "Ah, much better."

1
Конечно, предпочтительнее, например (вы, боги!), Обстреливать Питона. За исключением того, что я думаю, что проще и более общим правильно обрабатывать строку, которая содержит только пробелы. Слегка упрощенное выражение будет:^[[:space:]]*(.*[^[:space:]])?[[:space:]]*$
Рон Берк

4

Python имеет функцию, strip()которая работает идентично PHP trim(), поэтому мы можем просто сделать небольшой встроенный Python, чтобы сделать легко понятную утилиту для этого:

alias trim='python -c "import sys; sys.stdout.write(sys.stdin.read().strip())"'

Это урезает начальные и конечные пробелы (включая переводы строки).

$ x=`echo -e "\n\t   \n" | trim`
$ if [ -z "$x" ]; then echo hi; fi
hi

в то время как это работает, вы можете подумать о предложении решения, которое не требует запуска полного интерпретатора Python просто для обрезки строки. Это просто расточительно.
pdwalker

3
#!/bin/bash

function trim
{
    typeset trimVar
    eval trimVar="\${$1}"
    read trimVar << EOTtrim
    $trimVar
EOTtrim
    eval $1=\$trimVar
}

# Note that the parameter to the function is the NAME of the variable to trim, 
# not the variable contents.  However, the contents are trimmed.


# Example of use:
while read aLine
do
    trim aline
    echo "[${aline}]"
done < info.txt



# File info.txt contents:
# ------------------------------
# ok  hello there    $
#    another  line   here     $
#and yet another   $
#  only at the front$
#$



# Output:
#[ok  hello there]
#[another  line   here]
#[and yet another]
#[only at the front]
#[]

3

Я обнаружил, что мне нужно добавить некоторый код из грязного sdiffвывода, чтобы очистить его:

sdiff -s column1.txt column2.txt | grep -F '<' | cut -f1 -d"<" > c12diff.txt 
sed -n 1'p' c12diff.txt | sed 's/ *$//g' | tr -d '\n' | tr -d '\t'

Это удаляет завершающие пробелы и другие невидимые символы.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.