Як обрізати пробіл із змінної Bash?


920

У мене є сценарій оболонки з цим кодом:

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

Але умовний код завжди виконується, тому що hg stзавжди друкується принаймні один символ нового рядка.

  • Чи є простий спосіб позбавити пробіл $var(наприклад, trim()у PHP )?

або

  • Чи існує стандартний спосіб вирішення цього питання?

Я міг би використовувати sed або AWK , але хотілося б подумати, що існує більш елегантне рішення цієї проблеми.


3
Пов’язано, якщо ви хочете обрізати пробіл на ціле число і просто отримати ціле число, оберніть його $ (($ var)) і навіть можете це зробити, коли всередині подвійних лапок. Це стало важливим, коли я використовував заяву про дату та назви файлів.
Воломіке

"Чи існує стандартний спосіб вирішення цього питання?" Так, використовуйте [[замість [. $ 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- тому в кінці повинно бути більше, ніж просто новий рядок.
Mecki

A = "123 4 5 6"; В = echo $A | sed -r 's/( )+//g';
bruziuz

Відповіді:


1021

Давайте визначимо змінну, що містить пробіл проміжного, останнього та проміжного пробілів:

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 Сушкіна виглядала б так: 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.
примірник мене

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

@VictorZamanian Ваше рішення не працює, якщо вхід містить лише пробіл. Двокоректні рішення sed, задані MattyV та instanceof мені, працюють добре з лише вхідним простором.
Торбен

@Torben Справедлива точка. Я припускаю, що один вираз можна зробити умовним, з |тим, щоб зберегти його як один-єдиний вираз, а не кілька.
Віктор Заманян

965

Проста відповідь:

echo "   lol  " | xargs

Xargs зробить обрізку за вас. Це одна команда / програма, без параметрів, повертає обрізану рядок, просто так!

Примітка: це не видаляє всі внутрішні простори, тому "foo bar"залишається однаковим; це НЕ стає "foobar". Однак кілька пробілів будуть зведені до одиничних просторів, так "foo bar"і стануть "foo bar". Крім того, він не видаляє символи кінця рядків.


27
Приємно. Це працює дуже добре. Я вирішив передати це xargs echoлише для того, щоб бути багатослівним щодо того, що я роблю, але xargs самостійно використовує ехо за замовчуванням.
Буде чи

24
Хороший трюк, але будьте обережні, ви можете використовувати його для однорядного рядка, але - за допомогою дизайну xargs - він не буде просто обробляти багаторядковий контент. sed - це твій друг.
Jocelyn delalande

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 ділиться на чотири.
bos

64
Це погано. 1. Це перетвориться a<space><space>bна a<space>b. 2. Навіть більше: воно перетвориться a"b"c'd'eна abcde. 3. Навіть більше: вона вийде з ладу a"bі т. Д.
Саша

357

Існує рішення, яке використовує лише вбудовані 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.

Довідково


17
Розумний! Це моє улюблене рішення, оскільки воно використовує вбудовану функцію bash. Дякуємо за публікацію! @San, це два вкладених обрізки рядків. Наприклад, обрізав s=" 1 2 3 "; echo \""${s%1 2 3 }"\"би все з кінця, повертаючи ведучих " ". Subbing 1 2 3 з [![:space:]]*підкаже, щоб "знайти першого непросторового персонажа, а потім клобувати його та все після". Використання %%замість цього %робить обробку з кінця справою жадібною. Це вкладено в не жадібну обробку від початку, тому фактично ви підстригаєте " "з самого початку. Потім поміняйте місцями%, # і * для кінців пробілів. Бам!
Марк Г.

2
Я не знайшов жодних небажаних побічних ефектів, і основний код працює і з іншими оболонками, схожими на POSIX. Однак під Solaris 10 вона не працює /bin/sh(лише з /usr/xpg4/bin/sh, але це не те, що буде використано для звичайних скриптів sh).
vinc17

9
Набагато краще рішення, ніж використання sed, tr тощо, оскільки це набагато швидше, уникаючи будь-яких вилок (). Для Цигвіна різниця в швидкості - це порядки.
Гена Павловського

9
@San Спочатку я був спотиканий, тому що думав, що це регулярні вирази. Вони не. Скоріше, це синтаксис відповідності шаблонів ( gnu.org/software/bash/manual/html_node/Pattern-Matching.html , wiki.bash-hackers.org/syntax/pattern ), що використовується у видаленні підрядків ( 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
А точніше, він працює для пробілів посеред вару, але не тоді, коли я намагаюся закріпити його в кінці.
Пол Томблін

Чи допомагає це? На сторінці сторінки: "$ {parameter / pattern / string} [...] Якщо шаблон починається з%, він повинен відповідати в кінці розширеного значення параметра."

@Ant, значить, вони насправді не регулярні вирази, але щось подібне?
Пол Томблін

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 '
Джо

@Joe Я додав кращий варіант.
wjandrea

42

З розділу посібника Bash про глобалізацію

Використання 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
як ви вже спостерігали, обробка () видаляє лише пробіли та пробіли.
GuruM

Як mkelement вже зазначав, вам потрібно передати параметр функції як цитований рядок, тобто $ (обрізати "$ string") замість $ (trim $ string). Я оновив код, щоб показати правильне використання. Дякую.
ГуруМ

Наскільки я ціную , знаючи про можливості оболонки, я не думаю , що кінцевий результат є більш елегантним , ніж просто робити 2 модель замін
sehe

Зауважте, що (при досить новітній версії 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
Як щодо провідних пробілів?
Qian Chen

Це знімає весь пробіл sed -e 's/\s*$//'. Пояснення: 's' означає пошук, '\ s' означає весь пробіл, '*' означає нуль або багато, '$' означає до кінця рядка і '//' означає заміну всіх збігів порожнім рядком .
Крейг

У 's / * $ //', чому перед зірочкою замість одного пробілу є 2 пробіли? Це помилка?
Brent212


24

Якщо увімкнено розширені функції відповідності розширеного шаблону Баша ( shopt -s extglob), ви можете використовувати це:

{trimmed##*( )}

для видалення довільної кількості провідних пробілів.


Страшенно! Я думаю, що це найбільш легке та елегантне рішення.
сумнівним

1
Дивіться публікацію @ GuruM нижче про подібне, але більш загальне рішення, яке (а) стосується всіх форм білого простору та (b) також обробляє відсталий простір білого кольору.
mklement0

@mkelement +1 за те, що неполадки переписати фрагмент коду як функції. Спасибі
GuruM

Працює і з типовим / bin / ksh за замовчуванням OpenBSD. /bin/sh -o posixпрацює теж, але я підозріло.
Клінт Пахл

Тут не чарівний чарівник; що trimmed? Це вбудована річ або змінна, яку обробляють?
Abhijit Sarkar

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!}
Водолій Потужність

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 немає необхідності використовувати відлуння в підрозділі для однолінійної версії, просто read -rd '' str <<<"$str"це зробиться .
flabdablet

12

Відповідей дуже багато, але я все ж вважаю, що мій щойно написаний сценарій варто згадати, оскільки:

  • він був успішно випробуваний у оболонці bash / dash / busybox
  • це надзвичайно мало
  • це не залежить від зовнішніх команд і не потребує роздрібнення (-> швидке та низьке використання ресурсів)
  • він працює як очікувалося:
    • він знімає всі пробіли та вкладки від початку та до кінця, але не більше
    • важливо: він не видаляє нічого з середини рядка (багато інших відповідей), навіть нові рядки залишаться
    • спеціальне: "$*"приєднує кілька аргументів, використовуючи один пробіл. якщо ви хочете обрізати та вивести лише перший аргумент, використовуйте "$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, а іноді хочеться уникати виклику зовнішніх інструментів
Даніель Олдер

Щоб зробити код [\ \t]
зручнішим

@leondepeon ти це спробував? Я спробував, коли я написав це і спробував ще раз, і ваша пропозиція не працює ні в одному з баш, тире, зайнятих
Даніель Олдер,

@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}отримання детальної інформації. Також на цьому веб-сайті роз'яснюються схеми баш . Таким чином, ##засоби видаляють заданий візерунок спереду, а %%засоби видаляють даний візерунок ззаду. +( )Частина являє собою шаблон , і це означає «один або більше входження в просторі»
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). Крім того, цей підхід підлягає потенційно небажаному розширенню імені траєкторії (глобалізації).
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|

c) Заключна примітка:
Якщо вам не подобається використовувати функцію, для однорядкового рядка ви можете просто скористатися командою "легше запам'ятати", наприклад:

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<

Тож якщо ви проти затримати ці пробіли, будь ласка, скористайтеся функцією на початку моєї відповіді!

г) ПОЯСНЕННЯ синтаксису sed «знайти і замінити» на багаторядкових рядках, що використовуються всередині функції обрізки:

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, він не буде працювати для багаторядкових рядків, хоча він повинен працювати для однорядкових рядків.
GuruM

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 +, відповідність за замовчуванням НЕ спрацює, тому що регулярний вираз повинен бути не цитований для того, щоб для роботи та (b) сам регулярний вираз не обробляє випадок, коли вхідний рядок є єдиним, непробільним символом, оточеним пробілами. Щоб усунути ці проблеми, замініть ifрядок з: if [[ "$trimmed" =~ ' '*([^ ]|[^ ].*[^ ])' '* ]]. Нарешті, підхід стосується лише просторів, а не інших форм пробілів (див. Наступний мій коментар).
mklement0

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

6

Використовуйте AWK:

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

Солодке, що, здається, працює (колишнє:) $stripped_version=echo $ var | awk '{gsub (/ ^ + | + $ /, "")} 1'``
rogerdpack

4
окрім awk нічого не робить: echo'ing без котируваної змінної вже викреслив пробіл
glenn jackman

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.