Jak przyciąć białe znaki ze zmiennej Bash?


920

Mam skrypt powłoki z tym kodem:

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

Ale kod warunkowy zawsze wykonuje się, ponieważ hg stzawsze drukuje co najmniej jeden znak nowej linii.

  • Czy istnieje prosty sposób na usunięcie białych znaków $var(jak trim()w PHP )?

lub

  • Czy istnieje standardowy sposób radzenia sobie z tym problemem?

Mógłbym użyć sed lub AWK , ale chciałbym pomyśleć, że istnieje bardziej eleganckie rozwiązanie tego problemu.


3
Powiązane, jeśli chcesz przyciąć miejsce na liczbie całkowitej i po prostu uzyskać liczbę całkowitą, zawiń za pomocą $ (($ var)), a nawet możesz to zrobić, gdy wewnątrz cudzysłowu. Stało się to ważne, gdy użyłem instrukcji date i nazw plików.
Volomike,

„Czy istnieje standardowy sposób radzenia sobie z tym problemem?” Tak, użyj [[zamiast [. $ var=$(echo) $ [ -n $var ]; echo $? #undesired test return 0 $ [[ -n $var ]]; echo $? 1
user.friendly

Jeśli to pomaga, przynajmniej gdzie go testuję na Ubuntu 16.04. Korzystanie następujące mecze wykończenia pod każdym względem: echo " This is a string of char " | xargs. Jeśli jednak mają jeden cytat w tekście można wykonać następujące czynności: echo " This i's a string of char " | xargs -0. Zauważ, że wspominam o najnowszym xargs (4.6.0)
Luis Alvarado,

Warunek nie jest spełniony z powodu nowej linii, ponieważ backticks połykają ostatnią nową linię. To nic nie wydrukuje test=`echo`; if [ -n "$test" ]; then echo "Not empty"; fi, ale to zrobi test=`echo "a"`; if [ -n "$test" ]; then echo "Not empty"; fi- więc na końcu musi być coś więcej niż tylko nowa linia.
Mecki

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

Odpowiedzi:


1021

Zdefiniujmy zmienną zawierającą początkowe, końcowe i pośrednie białe znaki:

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

Jak usunąć wszystkie białe znaki (oznaczone przez [:space:]in 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

Jak usunąć tylko wiodące białe znaki:

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

Jak usunąć tylko końcowe białe znaki:

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

Jak usunąć zarówno początkowe, jak i końcowe spacje - połącz łańcuchy seds:

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

Alternatywnie, jeśli bash obsługuje tę funkcję, można wymienić echo -e "${FOO}" | sed ...ze sed ... <<<${FOO}jak tak (za końcowe białe znaki):

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

63
Uogólniać rozwiązanie do obsługi wszystkich form spacją, zastąpi znak spacji w tri sedpolecenia z [[:space:]]. Należy pamiętać, że sedpodejście będzie działać tylko w przypadku wprowadzania jednowierszowego . Podejścia, które działają z wejściem wieloliniowym, a także wykorzystują wbudowane funkcje bash, zobacz odpowiedzi @bashfu i @GuruM. Uogólniona, wbudowana wersja rozwiązania @Nicholas Sushkin wyglądałaby następująco: trimmed=$([[ " test test test " =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]; echo -n "${BASH_REMATCH[1]}")
mklement0

7
Jeśli robisz to często, dołączanie alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'"do twojego ~/.profilepozwala ci używać echo $SOMEVAR | trimi cat somefile | trim.
instanceof me

Napisałem sedrozwiązanie, które wykorzystuje tylko jeden wyraz zamiast dwóch: sed -r 's/^\s*(\S+(\s+\S+)*)\s*$/\1/'. Przycina wiodące i końcowe białe znaki oraz przechwytuje sekwencje znaków innych niż białe znaki w środku. Cieszyć się!
Victor Zamanian

@VictorZamanian Twoje rozwiązanie nie działa, jeśli dane wejściowe zawierają tylko białe znaki. Rozwiązania sed z dwoma wzorami podane przez MattyV i instanceof me działają dobrze przy wprowadzaniu tylko białych znaków.
Torben

@Torben Fair point. Podejrzewam, że pojedyncze wyrażenie może być warunkowe, |tak aby zachować je jako jedno wyrażenie, a nie kilka.
Victor Zamanian

965

Prosta odpowiedź to:

echo "   lol  " | xargs

Xargs wykona dla Ciebie przycinanie. To jedno polecenie / program, bez parametrów, zwraca przycięty ciąg znaków, to proste!

Uwaga: to nie usuwa wszystkich wewnętrznych spacji, więc "foo bar"pozostaje takie samo; NIE staje się "foobar". Jednak wiele spacji zostanie skondensowanych do pojedynczych spacji, więc "foo bar"stanie się "foo bar". Ponadto nie usuwa znaków końca linii.


27
Miły. To działa naprawdę dobrze. Zdecydowałem się wyrzucić to po xargs echoto, żeby mówić o tym, co robię, ale xargs samo w sobie domyślnie używa echa.
Czy

24
Fajna sztuczka, ale bądź ostrożny, możesz użyć go do ciągów jednowierszowych, ale −by xargs design - nie będzie to tylko przycinanie z zawartością potoków wieloliniowych. sed jest więc twoim przyjacielem.
Jocelyn delalande

22
Jedyny problem z xargs polega na tym, że wprowadzi on nową linię, jeśli chcesz ją wyłączyć, poleciłbym sed 's/ *$//'jako alternatywę. Możesz zobaczyć xargsnową linię w ten sposób: echo -n "hey thiss " | xargs | hexdump zauważysz, 0a73że ajest to nowa linia. Jeśli zrobisz to samo z sed: echo -n "hey thiss " | sed 's/ *$//' | hexdumpzobaczysz 0073, nie ma nowej linii.

8
Ostrożny; to się mocno zepsuje, jeśli ciąg znaków do xargs zawiera nadmiar spacji pomiędzy nimi. Jak „to jest jeden argument”. xargs podzieli się na cztery.
bos

64
To jest złe. 1. Zamieni się a<space><space>bw a<space>b. 2. Co więcej: zamieni się a"b"c'd'ew abcde. 3. Co więcej: zawiedzie a"b, itp.
Sasha

357

Istnieje rozwiązanie wykorzystujące tylko wbudowane Bash zwane symbolami wieloznacznymi :

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

Oto to samo zawinięte w funkcję:

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

Podajesz ciąg do przycięcia w formie cytowanej. na przykład:

trim "   abc   "

Zaletą tego rozwiązania jest to, że będzie ono działać z dowolną powłoką zgodną z POSIX.

Odniesienie


17
Sprytny! To moje ulubione rozwiązanie, ponieważ korzysta z wbudowanej funkcji bash. Dzięki za opublikowanie! @ San, to dwa zagnieżdżone przycinanie strun. Np. s=" 1 2 3 "; echo \""${s%1 2 3 }"\"Przycinałby wszystko od końca, zwracając prowadzenie " ". Subbing 1 2 3 z [![:space:]]*mówi mu, aby „znalazł pierwszą postać niebędącą spacją, a następnie ją przerobił i wszystko po”. Użycie %%zamiast %powoduje, że operacja przycinania od końca jest zachłanna. Jest to zagnieżdżone w niechcianym przycinaniu od początku, więc w efekcie przycinasz " "od początku. Następnie zamień%, # i * na spacje końcowe. Bam!
Mark G.

2
Nie znalazłem żadnych niepożądanych efektów ubocznych, a główny kod działa również z innymi powłokami podobnymi do POSIX. Jednak w systemie Solaris 10 nie działa /bin/sh(tylko z /usr/xpg4/bin/sh, ale nie to będzie używane ze zwykłymi skryptami sh).
vinc17

9
Znacznie lepsze rozwiązanie niż używanie sed, tr itp., Ponieważ jest znacznie szybsze, unikając jakiegokolwiek fork (). Na Cygwinie różnica prędkości jest rzędami wielkości.
Gene Pavlovsky,

9
@San Na początku byłem zakłopotany, ponieważ myślałem, że to wyrażenia regularne. Oni nie są. Jest to raczej składnia dopasowywania wzorców ( gnu.org/software/bash/manual/html_node/Pattern-Matching.html , wiki.bash-hackers.org/syntax/pattern ) używana w usuwaniu podciągów ( tldp.org/LDP/abs /html/string-manipulation.html ). Tak ${var%%[![:space:]]*}mówi „usuń z varjego najdłuższego podłańcucha, który zaczyna się od znaku spacji”. Oznacza to, że masz tylko wiodące miejsca, które następnie usuwasz ${var#... Następująca linia (końcowa) jest odwrotna.
Ohad Schneider

8
Jest to w przeważającej mierze idealne rozwiązanie. Rozwidlone jeden lub więcej procesów zewnętrznych (na przykład awk, sed, tr, xargs) jedynie przyciąć spacji z pojedynczego łańcucha jest całkowicie niepoczytalny - szczególnie gdy większość powłok (w tym bash) już zapewniają natywną ciąg munging zaplecze out-of-the-box.
Cecil Curry

81

Bash ma funkcję zwaną rozszerzaniem parametrów , która między innymi umożliwia zamianę ciągów w oparciu o tak zwane wzorce (wzorce przypominają wyrażenia regularne, ale istnieją podstawowe różnice i ograniczenia). [oryginalny wiersz flussence: Bash ma wyrażenia regularne, ale są dobrze ukryte:]

Poniżej pokazano, jak usunąć całą spację (nawet z wnętrza) ze zmiennej wartości.

$ 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
A raczej działa w przypadku spacji w środku zmiennej, ale nie wtedy, gdy próbuję zakotwiczyć ją na końcu.
Paul Tomblin,

Czy to pomaga? Z strony podręcznika: „$ {parametr / wzór / ciąg} [...] Jeśli wzorzec zaczyna się od%, musi pasować na końcu rozszerzonej wartości parametru.”

@Ant, więc nie są to tak naprawdę wyrażenia regularne, ale coś podobnego?
Paul Tomblin,

3
Są wyrażeniami regularnymi, tylko dziwnym dialektem.

13
${var/ /}usuwa pierwszy znak spacji. ${var// /}usuwa wszystkie znaki spacji. Nie ma możliwości przycięcia tylko wiodących i końcowych białych znaków za pomocą tylko tej konstrukcji.
Gilles 'SO - przestań być zły'

60

Aby usunąć wszystkie spacje od początku i końca ciągu (w tym znaki końca wiersza):

echo $variable | xargs echo -n

Spowoduje to również usunięcie duplikatów:

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

Tworzy: „ten ciąg ma wiele spacji”


5
Zasadniczo xargs usuwa wszystkie ograniczniki z łańcucha. Domyślnie używa spacji jako separatora (można to zmienić opcją -d).
rkachach

4
To zdecydowanie najczystsze (zarówno krótkie, jak i czytelne) rozwiązanie.
Potherca

Dlaczego w ogóle potrzebujesz echo -n? echo " my string " | xargsma taką samą moc wyjściową.
bfontaine

echo -n usuwa również koniec linii
rkachach

55

Usuń jedną przestrzeń wiodącą i jedną końcową

trim()
{
    local trimmed="$1"

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

    echo "$trimmed"
}

Na przykład:

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

Wynik:

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

Usuń wszystkie wiodące i końcowe spacje

trim()
{
    local trimmed="$1"

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

    echo "$trimmed"
}

Na przykład:

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

Wynik:

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

9
Spowoduje to przycięcie tylko 1 znaku spacji. Tak więc echo skutkuje'hello world ', 'foo bar', 'both sides '
Joe

@Joe Dodałem lepszą opcję.
wjandrea,

42

Z sekcji Przewodnik po Bash na temat globowania

Aby użyć extglob w rozszerzeniu parametru

 #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  

Oto ta sama funkcjonalność zawarta w funkcji (UWAGA: Należy zacytować ciąg wejściowy przekazany do funkcji):

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.
}

Stosowanie:

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

Jeśli zmienimy funkcję, aby była wykonywana w podpowłoce, nie musimy się martwić o sprawdzenie bieżącej opcji powłoki dla extglob, możemy po prostu ustawić ją bez wpływu na bieżącą powłokę. Upraszcza to ogromnie tę funkcję. Aktualizuję również parametry pozycyjne „na miejscu”, więc nawet nie potrzebuję zmiennej lokalnej

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

więc:

$ 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
jak zauważyłeś trim () usuwa tylko początkowe i końcowe białe spacje.
GuruM

Jak zauważył już mkelement, musisz przekazać parametr funkcji jako ciąg cytowany, tj. $ (Przycinanie „$ ciąg”) zamiast $ (przycinanie $ ciąg). Zaktualizowałem kod, aby pokazać prawidłowe użycie. Dzięki.
GuruM

Chociaż doceniam wiedzę o opcjach powłoki, nie sądzę, że efekt końcowy jest bardziej elegancki niż zwykłe wykonanie 2
zamian

Zauważ, że (z dość najnowszą wersją Bash?) Możesz uprościć mechanizm przywracania opcji extglob, używając shopt -p: po prostu napisz local restore="$(shopt -p extglob)" ; shopt -s extglobna początku swojej funkcji i eval "$restore"na końcu (z wyjątkiem tego, że eval jest złe…).
Maëlan,

Świetne rozwiązanie! Jedna potencjalna poprawa: wygląda na to, że [[:space:]]można ją zastąpić spacją: ${var##+( )}a także ${var%%+( )}pracować i są łatwiejsze do odczytania.
DKroot

40

Możesz przycinać po prostu za pomocą echo:

foo=" qsdqsd qsdqs q qs   "

# Not trimmed
echo \'$foo\'

# Trim
foo=`echo $foo`

# Trimmed
echo \'$foo\'

Spada to wiele sąsiadujących ze sobą spacji w jedno.
Evgeni Sergeev,

7
Próbowałeś, gdy foozawiera symbol wieloznaczny? np. foo=" I * have a wild card"... niespodzianka! Ponadto łączy to kilka sąsiadujących ze sobą przestrzeni w jedną.
gniourf_gniourf

5
Jest to doskonałe rozwiązanie, jeśli: 1. nie chcesz spacji na końcach 2. chcesz tylko jednej spacji między każdym słowem 3. pracujesz z kontrolowanym wejściem bez symboli wieloznacznych. Zasadniczo zamienia źle sformatowaną listę w dobrą.
musicin3d

Dobre przypomnienie symboli wieloznacznych @gniourf_gniourf +1. Nadal doskonałe rozwiązanie, Vamp. Tobie też +1.
Dr Beco

25

Zawsze robiłem to z sedem

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

Jeśli istnieje bardziej eleganckie rozwiązanie, mam nadzieję, że ktoś je opublikuje.


czy mógłbyś wyjaśnić składnię sed?
farid99

2
Wyrażenie regularne dopasowuje wszystkie końcowe białe znaki i zastępuje je niczym.
Paul Tomblin

4
Co powiesz na wiodące białe znaki?
Qian Chen,

To usuwa wszystkie końcowe białe znaki sed -e 's/\s*$//'. Objaśnienie: „s” oznacza wyszukiwanie, „\ s” oznacza całą białą spację, „*” oznacza zero lub wiele, „$” oznacza do końca wiersza, a „//” oznacza zastąpienie wszystkich dopasowań pustym łańcuchem .
Craig

Dlaczego w „s / * $ //” przed gwiazdką są 2 spacje zamiast pojedynczej spacji? Czy to literówka?
Brent212

24

Możesz usunąć nowe linie za pomocą tr:

var=`hg st -R "$path" | tr -d '\n'`
if [ -n $var ]; then
    echo $var
done

8
Nie chcę usuwać „\ n” ze środka łańcucha, tylko od początku lub końca.
za dużo php

24

Po włączeniu funkcji rozszerzonego dopasowania wzorca Bash ( shopt -s extglob) możesz użyć tego:

{trimmed##*( )}

aby usunąć dowolną liczbę wiodących spacji.


Przerażający! Myślę, że to najbardziej lekkie i eleganckie rozwiązanie.
dubiousjim

1
Zobacz post @ GuruM poniżej, aby znaleźć podobne, ale bardziej ogólne rozwiązanie, które (a) radzi sobie ze wszystkimi formami białych znaków i (b) obsługuje również końcowe białe znaki .
mklement0

@mkelement +1 za podjęcie problemu z przepisaniem mojego fragmentu kodu jako funkcji. Dzięki
GuruM,

Działa również z domyślnym / bin / ksh OpenBSD. /bin/sh -o posixteż działa, ale jestem podejrzliwy.
Clint Pachl

Tu nie ma czarodzieja; co jest trimmed? Czy jest to rzecz wbudowana czy zmienna, która jest przycinana?
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
Niesamowity! Prosty i skuteczny! Oczywiście moje ulubione rozwiązanie. Dziękuję Ci!
xebeche

1
@CraigMcQueen jest to wartość zmiennej, ponieważ readbędzie przechowywana w zmiennej pod nazwą 1 $ skrócona wersja wartości $ {! 1}
Aquarius Power

2
Parametrem funkcji trim () jest nazwa zmiennej: patrz wywołanie trim () wewnątrz test_trim (). W ramach trim (), jak wywoływane z test_trim (), $ 1 rozwija się do foo, a $ {! 1} rozwija się do $ foo (to znaczy do bieżącej zawartości zmiennej foo). Wyszukaj w podręczniku bash hasło „zmienna pośrednia”.
flabdablet

1
Co powiesz na tę niewielką modyfikację, aby obsługiwać przycinanie wielu zmiennych w jednym połączeniu? trim() { while [[ $# -gt 0 ]]; do read -rd '' $1 <<<"${!1}"; shift; done; }
Gene Pavlovsky,

2
@AquariusPower nie ma potrzeby używania echa w podpowłoce dla wersji jednowierszowej, wystarczy read -rd '' str <<<"$str".
flabdablet

12

Istnieje wiele odpowiedzi, ale nadal uważam, że mój właśnie napisany skrypt jest wart wspomnienia, ponieważ:

  • został pomyślnie przetestowany w powłoce powłoki bash / dash / busybox
  • jest bardzo mały
  • nie zależy od poleceń zewnętrznych i nie musi rozwidlać (-> szybkie i niskie zużycie zasobów)
  • działa zgodnie z oczekiwaniami:
    • usuwa wszystkie spacje i tabulatory od początku i na końcu, ale nie więcej
    • ważne: nie usuwa niczego ze środka ciągu (robi to wiele innych odpowiedzi), nawet nowe wiersze pozostaną
    • special: "$*"łączy wiele argumentów za pomocą jednej spacji. jeśli chcesz przyciąć i wypisać tylko pierwszy argument, użyj "$1"zamiast niego
    • jeśli nie ma żadnych problemów z dopasowaniem wzorców nazw plików itp

Scenariusz:

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"
}

Stosowanie:

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

Wynik:

>here     is
    something<

Bah in C byłoby to o wiele prostsze do wdrożenia!
Nils

Pewnie. Niestety nie jest to C i czasami chcesz uniknąć wywoływania zewnętrznych narzędzi
Daniel Alder

Aby kod był bardziej czytelny i kompatybilny z [\ \t]
funkcją

@leondepeon próbowałeś tego? Próbowałem, kiedy to napisałem i próbowałem ponownie, a twoja sugestia nie działa w żadnym z bash, dash, busybox
Daniel Alder

@DanielAlder Zrobiłem, ale ponieważ już 3 lata temu, nie mogę znaleźć kodu, w którym go użyłem. Teraz jednak prawdopodobnie [[:space:]]
użyłbym

11

Możesz użyć starej szkoły tr. Na przykład zwraca liczbę zmodyfikowanych plików w repozytorium git, bez białych znaków.

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

1
Nie przycina białych znaków z przodu iz tyłu - usuwa wszystkie białe znaki z łańcucha.
Nick

11

To działało dla mnie:

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>

Aby umieścić to w mniejszej liczbie wierszy dla tego samego wyniku:

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

1
Nie działało dla mnie. Pierwszy wydrukował nieobszyty sznurek. Drugi rzucił złe zamiany. Czy możesz wyjaśnić, co się tutaj dzieje?
musicin3d

1
@ musicin3d: jest to strona, z której często korzystam, która wyjaśnia, jak działa zmienna manipulacja w poszukiwaniu bashów, aby ${var##Pattern}uzyskać więcej szczegółów. Ponadto ta strona wyjaśnia wzorce bash . Tak więc ##środki usuwają dany wzór z przodu, a %%środki usuwają dany wzór z tyłu. +( )Część jest wzorem, a to oznacza „jeden lub więcej występowanie miejsca”
gMale

Zabawne, zadziałało w trybie zachęty, ale nie po transponowaniu do pliku skryptu bash.
Dr Beco

dziwne. Czy w obu przypadkach jest to ta sama wersja bash?
gMale

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

LUB

# 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")")"
}

LUB

# 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]}"
}

LUB

# 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")"
}

LUB

Opierając się na expr soulution moskita ...

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

LUB

# 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

Widziałem, że skrypty po prostu wykorzystują zmienne przypisanie do wykonania zadania:

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

Biała spacja jest automatycznie łączona i przycinana. Trzeba uważać na metaznaki powłoki (potencjalne ryzyko wstrzyknięcia).

Poleciłbym również zawsze podwójne cytowanie podstawień zmiennych w warunkach powłoki:

if [ -n "$var" ]; then

ponieważ coś takiego jak -o lub inna treść w zmiennej może zmienić twoje argumenty testowe.


3
Jest to niekwotowane użycie $xyzz echotym, że łączy się biała spacja, a nie przypisanie zmiennej. Aby zapisać przyciętą wartość w zmiennej w twoim przykładzie, musisz użyć xyz=$(echo -n $xyz). Również to podejście podlega potencjalnie niepożądanemu rozszerzaniu nazw ścieżek (globbing).
mklement0

to jutro źle, wartość xyzzmiennej NIE jest przycinana.
caesarsol

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

1
To nie zadziała, jeśli pomiędzy dowolnymi dwoma słowami jest więcej niż jedna spacja. Spróbuj: echo $(echo "1 2 3")(z dwoma spacjami między 1, 2 i 3).
joshlf

7

Po prostu użyłbym sed:

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

a) Przykład użycia ciągu jednowierszowego

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

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

Wynik:

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

b) Przykład użycia ciągu wieloliniowego

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

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

Wynik:

GIVEN STRING: |    wordAA
   >wordB<
wordC    |

TRIMMED STRING: |wordAA
   >wordB<
wordC|

c) Uwaga końcowa:
jeśli nie chcesz używać funkcji, w przypadku ciągu jednowierszowego możesz po prostu użyć polecenia „łatwiej zapamiętać”, takiego jak:

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

Przykład:

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

Wynik:

wordA wordB wordC

Używanie powyższego w ciągach wielowierszowych również będzie działać , ale pamiętaj, że spowoduje to również odcięcie końcowej / wiodącej wewnętrznej przestrzeni wielokrotnej, jak zauważył GuruM w komentarzach

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

Wynik:

wordAA
>four spaces before<
>one space before<

Więc jeśli masz ochotę zachować te spacje, skorzystaj z funkcji na początku mojej odpowiedzi!

d) OBJAŚNIENIE składni sed „znajdź i zamień” na ciągi wielowierszowe używane wewnątrz wykończenia funkcji:

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
}'

Uwaga: Jak sugeruje @mkelement, nie będzie on działał w przypadku ciągów wieloliniowych, chociaż powinien działać w przypadku ciągów jednowierszowych.
GuruM,

1
Mylisz się: działa również na ciągi wieloliniowe. Po prostu przetestuj to! :)
Luca Borrione

+1 za użytkowanie - ułatwiło mi przetestowanie kodu. Jednak kod nadal nie działa w przypadku ciągów wieloliniowych. Jeśli przyjrzysz się uważnie wynikowi, zauważysz, że usuwane są również wszelkie wiodące / końcowe spacje wewnętrzne, np. Przestrzeń przed „wieloliniową” jest zastępowana przez „wieloliniową”. Po prostu spróbuj zwiększyć liczbę początkowych / końcowych spacji w każdej linii.
GuruM,

Teraz rozumiem, co masz na myśli! Dziękuję za awans, zredagowałem swoją odpowiedź.
Luca Borrione

@ „Luca Borrione” - witamy :-) Czy wyjaśniłbyś składnię sed używaną w trim ()? Może także pomóc każdemu użytkownikowi twojego kodu dostosować go do innych zastosowań. Może to nawet pomóc znaleźć przypadki krawędzi dla wyrażenia regularnego.
GuruM

6

Oto funkcja trim (), która przycina i normalizuje białe znaki

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

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

I inny wariant wykorzystujący wyrażenia regularne.

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

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

Pierwsze podejście jest trudne, ponieważ nie tylko normalizuje wewnętrzne białe znaki (zastępuje wszystkie wewnętrzne zakresy białych znaków pojedynczą spacją), ale podlega także globowaniu (rozwijaniu nazw ścieżek), tak że na przykład *znak w ciągu wejściowym rozwiń do wszystkich plików i folderów w bieżącym folderze roboczym. Na koniec, jeśli $ IFS jest ustawione na wartość inną niż domyślna, przycinanie może nie działać (choć łatwo temu zaradzić poprzez dodanie local IFS=$' \t\n'). Przycinanie jest ograniczone do następujących form białych znaków: spacji \ti \nznaków.
mklement0

1
Drugie podejście oparte na wyrażeniach regularnych jest świetne i pozbawione skutków ubocznych, ale w obecnej formie jest problematyczne: (a) w bash v3.2 +, dopasowanie domyślnie NIE zadziała, ponieważ wyrażenie regularne musi być UNquote w kolejności do pracy i (b) samo wyrażenie regularne nie obsługuje przypadku, w którym ciąg wejściowy jest pojedynczym znakiem spacji otoczonym spacjami. Aby rozwiązać te problemy, należy wymienić iflinię z: if [[ "$trimmed" =~ ' '*([^ ]|[^ ].*[^ ])' '* ]]. Wreszcie podejście to dotyczy tylko spacji, a nie innych form białych znaków (patrz mój następny komentarz).
mklement0

2
Funkcja wykorzystująca wyrażenia regularne zajmuje się tylko spacjami, a nie innymi formami białych znaków, ale łatwo ją uogólnić: Zamień ifwiersz na:[[ "$trimmed" =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]
mklement0

6

Użyj AWK:

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

Sweet, który wydaje się działać (ex :) $stripped_version=echo $ var | awk '{gsub (/ ^ + | + $ /, "")} 1' '
rogerdpack

4
poza tym, że awk nic nie robi: echo nie cytowanej zmiennej już
usunęło

6

Przypisania ignorują wiodące i końcowe białe znaki i jako takie mogą być używane do przycinania:

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

8
To nieprawda. To „echo” usuwa białe spacje, a nie przypisanie. W swoim przykładzie wykonaj, echo "$var"aby zobaczyć wartość ze spacjami.
Nicholas Sushkin

2
@NicholasSushkin Można zrobić, var=$(echo $var)ale nie polecam. Preferowane są inne przedstawione tutaj rozwiązania.
xebeche

5

Nie ma to problemu z niechcianym globowaniem, ponadto wewnętrzna biała przestrzeń jest niezmodyfikowana (zakładając, że $IFSjest ustawiona na wartość domyślną, która jest' \t\n' ).

Czyta do pierwszej nowej linii (i nie obejmuje jej) lub do końca łańcucha, w zależności od tego, co nastąpi wcześniej, i usuwa wszelką kombinację początkowej i końcowej spacji oraz \tznaków. Jeśli chcesz zachować wiele linii (a także rozdzielić początkowe i końcowe znaki nowej linii), użyj read -r -d '' var << eofzamiast tego; zwróć jednak uwagę, że jeśli twoje dane wejściowe zawierają \neof, zostaną odcięte tuż przed. (Inne formy białych znaków, a mianowicie \r, \fi nie\v są usuwane, nawet jeśli dodasz je do $ IFS.)

read -r var << eof
$var
eof


5

Spowoduje to usunięcie wszystkich białych znaków z łańcucha,

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

/zastępuje pierwsze wystąpienie i //wszystkie wystąpienia białych znaków w ciągu. To znaczy wszystkie białe spacje zostaną zastąpione przez - nic


4

To najprostsza metoda, jaką widziałem. Używa tylko Bash, to tylko kilka wierszy, wyrażenie regularne jest proste i pasuje do wszystkich form białych znaków:

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

Oto przykładowy skrypt do przetestowania:

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
Na pewno lepiej niż, na przykład (wy, bogowie!), Wystrzelić do Pythona. Z wyjątkiem tego, że uważam, że łatwiej i bardziej ogólnie jest poprawnie obsługiwać ciąg znaków, który zawiera tylko spacje. Nieco uproszczonym wyrażeniem byłoby:^[[:space:]]*(.*[^[:space:]])?[[:space:]]*$
Ron Burk

4

Python ma funkcję, strip()która działa identycznie jak PHP trim(), więc możemy po prostu zrobić trochę wbudowanego Pythona, aby stworzyć łatwo zrozumiałe narzędzie do tego:

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

Spowoduje to przycięcie początkowych i końcowych białych znaków (w tym znaków nowej linii).

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

podczas gdy to działa, możesz rozważyć zaproponowanie rozwiązania, które nie wymaga uruchomienia pełnego interpretera Pythona tylko w celu przycięcia łańcucha. To po prostu marnotrawstwo.
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

Odkryłem, że muszę dodać trochę kodu z nieporządnego sdiffwyjścia, aby go wyczyścić:

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'

Usuwa to końcowe spacje i inne niewidzialne znaki.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.