Wie schneide ich Leerzeichen aus einer Bash-Variablen?


920

Ich habe ein Shell-Skript mit diesem Code:

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

Der bedingte Code wird jedoch immer ausgeführt, da hg stimmer mindestens ein Zeilenumbruchzeichen gedruckt wird.

  • Gibt es eine einfache Möglichkeit, Leerzeichen zu entfernen $var(wie trim()in PHP )?

oder

  • Gibt es eine Standardmethode für den Umgang mit diesem Problem?

Ich könnte sed oder AWK verwenden , aber ich würde gerne glauben, dass es eine elegantere Lösung für dieses Problem gibt.


3
Wenn Sie das Leerzeichen einer Ganzzahl kürzen und nur die Ganzzahl abrufen möchten, schließen Sie mit $ (($ var)) ein und können dies sogar in doppelten Anführungszeichen tun. Dies wurde wichtig, als ich die Datumsangabe und Dateinamen verwendete.
Volomike

"Gibt es eine Standardmethode, um mit diesem Problem umzugehen?" Ja, verwenden Sie [[anstelle von [. $ var=$(echo) $ [ -n $var ]; echo $? #undesired test return 0 $ [[ -n $var ]]; echo $? 1
Benutzerfreundlich

Wenn es hilft, zumindest wo ich es unter Ubuntu 16.04 teste. Verwenden Sie die folgenden Übereinstimmungen in jeder Hinsicht : echo " This is a string of char " | xargs. Wenn Sie jedoch ein einfaches Anführungszeichen im Text haben, können Sie Folgendes tun : echo " This i's a string of char " | xargs -0. Beachten Sie, dass ich die neuesten xargs (4.6.0)
Luis Alvarado

Die Bedingung ist aufgrund einer neuen Zeile nicht wahr, da Backticks die letzte neue Zeile verschlucken. Dies wird nichts drucken test=`echo`; if [ -n "$test" ]; then echo "Not empty"; fi, dies wird jedoch test=`echo "a"`; if [ -n "$test" ]; then echo "Not empty"; fi- also muss am Ende mehr als nur eine neue Zeile stehen.
Mecki

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

Antworten:


1021

Definieren wir eine Variable, die führende, nachfolgende und mittlere Leerzeichen enthält:

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

So entfernen Sie alle Leerzeichen (gekennzeichnet mit [: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

So entfernen Sie nur führende Leerzeichen:

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

So entfernen Sie nur nachgestellte Leerzeichen:

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

So entfernen Sie sowohl führende als auch nachfolgende Leerzeichen - verketten Sie die 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

Alternativ kann , wenn Ihre bash unterstützt wird , können Sie ersetzen echo -e "${FOO}" | sed ...mit sed ... <<<${FOO}, wie so (für nachfolgende Leerzeichen):

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

63
Um die Lösung für alle Formen von Leerzeichen zu verallgemeinern , ersetzen Sie das Leerzeichen in den Befehlen trund seddurch [[:space:]]. Beachten Sie, dass der sedAnsatz nur für einzeilige Eingaben funktioniert . Informationen zu Ansätzen, die mit mehrzeiligen Eingaben funktionieren und auch die integrierten Funktionen von bash verwenden, finden Sie in den Antworten von @bashfu und @GuruM. Eine verallgemeinerte Inline-Version von @Nicholas Sushkins Lösung würde folgendermaßen aussehen: trimmed=$([[ " test test test " =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]; echo -n "${BASH_REMATCH[1]}")
mklement0

7
Wenn Sie dies häufig tun, können Sie durch Anhängen alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'"an Ihre und verwenden . ~/.profileecho $SOMEVAR | trimcat somefile | trim
Instanz von mir

Ich habe eine sedLösung geschrieben, die nur einen einzigen Ausdruck anstelle von zwei verwendet : sed -r 's/^\s*(\S+(\s+\S+)*)\s*$/\1/'. Es schneidet führende und nachfolgende Leerzeichen ab und erfasst alle durch Leerzeichen getrennten Sequenzen von Nicht-Leerzeichen in der Mitte. Genießen!
Victor Zamanian

@ VictorZamanian Ihre Lösung funktioniert nicht, wenn die Eingabe nur Leerzeichen enthält. Die Zwei-Muster-Sed-Lösungen von MattyV und meiner Instanz funktionieren gut mit nur Leerzeichen.
Torben

@Torben Fairer Punkt. Ich nehme an, der einzelne Ausdruck könnte mit bedingt gemacht werden, |um ihn als einen einzelnen Ausdruck und nicht als mehrere auszudrücken.
Victor Zamanian

966

Eine einfache Antwort lautet:

echo "   lol  " | xargs

Xargs erledigt das Trimmen für Sie. Es ist ein Befehl / Programm, keine Parameter, gibt den zugeschnittenen String zurück, so einfach ist das!

Hinweis: Dadurch werden nicht alle internen Leerzeichen entfernt, sodass sie "foo bar"gleich bleiben. es wird NICHT "foobar". Es werden jedoch mehrere Räume zu einzelnen Räumen zusammengefasst, so "foo bar"dass dies auch der Fall ist "foo bar". Außerdem werden keine Zeilenendezeichen entfernt.


27
Nett. Das funktioniert wirklich gut. Ich habe beschlossen, es zu xargs echopfeifen, um ausführlich zu sagen, was ich tue, aber xargs alleine verwendet standardmäßig Echo.
Will

24
Netter Trick, aber seien Sie vorsichtig, Sie können ihn für einzeilige Zeichenfolgen verwenden, aber - durch Xargs-Design - wird er nicht nur mit mehrzeiligen Piping-Inhalten zugeschnitten. sed ist dann dein Freund.
Jocelyn delalande

22
Das einzige Problem mit xargs ist, dass es eine neue Zeile einführt. Wenn Sie die neue Zeile fernhalten möchten, würde ich dies sed 's/ *$//'als Alternative empfehlen . Sie können die sehen xargsNewline wie folgt aus : echo -n "hey thiss " | xargs | hexdump Sie werden feststellen , 0a73das adie Neue - Zeile ist. Wenn Sie dasselbe tun mit sed: echo -n "hey thiss " | sed 's/ *$//' | hexdumpSie werden sehen 0073, keine neue Zeile.

8
Vorsichtig; Dies wird hart brechen, wenn die Zeichenfolge zu xargs überschüssige Leerzeichen dazwischen enthält. Wie "das ist ein Argument". xargs wird in vier geteilt.
Bos

64
Das ist schlecht. 1. Es wird a<space><space>bin a<space>b. 2. Noch mehr: es wird sich a"b"c'd'ein abcde. 3. Noch mehr: es wird am a"busw. scheitern
Sasha

358

Es gibt eine Lösung, die nur Bash-integrierte Funktionen verwendet, die als Platzhalter bezeichnet werden :

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

Hier ist das gleiche in einer Funktion verpackt:

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

Sie übergeben die zu schneidende Zeichenfolge in Anführungszeichen. z.B:

trim "   abc   "

Das Schöne an dieser Lösung ist, dass sie mit jeder POSIX-kompatiblen Shell funktioniert.

Referenz


18
Klug! Dies ist meine Lieblingslösung, da sie die integrierte Bash-Funktionalität verwendet. Danke fürs Schreiben! @ San, es sind zwei verschachtelte String-Trims. ZB s=" 1 2 3 "; echo \""${s%1 2 3 }"\"würde alles vom Ende abschneiden und die Führung zurückgeben " ". Das Subbing 1 2 3 mit [![:space:]]*sagt ihm, er solle "das erste Nicht-Leerzeichen finden, es dann und alles danach überfallen". Die Verwendung von %%anstelle von %macht den Trim-from-End-Vorgang gierig. Dies ist in einem nicht gierigen Trimmen von Anfang an verschachtelt, sodass Sie praktisch " "von Anfang an trimmen . Tauschen Sie dann%, # und * gegen die Endfelder aus. Bam!
Mark G.

2
Ich habe keine unerwünschten Nebenwirkungen gefunden und der Hauptcode funktioniert auch mit anderen POSIX-ähnlichen Shells. Unter Solaris 10 funktioniert es jedoch nicht mit /bin/sh(nur mit /usr/xpg4/bin/sh, aber dies wird nicht mit üblichen sh-Skripten verwendet).
vinc17

9
Viel bessere Lösung als die Verwendung von sed, tr usw., da es viel schneller ist und keine Gabel enthält (). Bei Cygwin beträgt der Geschwindigkeitsunterschied Größenordnungen.
Gene Pavlovsky

9
@ San Zuerst war ich ratlos, weil ich dachte, das wären reguläre Ausdrücke. Sie sind nicht. Dies ist vielmehr die Pattern Matching-Syntax ( gnu.org/software/bash/manual/html_node/Pattern-Matching.html , wiki.bash-hackers.org/syntax/pattern ), die beim Entfernen von Teilzeichenfolgen ( tldp.org/LDP/abs ) verwendet wird /html/string-manipulation.html ). So ${var%%[![:space:]]*}heißt es "Entfernen Sie aus varder längsten Teilzeichenfolge, die mit einem Nicht-Leerzeichen beginnt". Das heißt, Sie haben nur noch die führenden Leerzeichen, mit denen Sie anschließend entfernen ${var#... Die folgende Zeile (nachlaufend) ist das Gegenteil.
Ohad Schneider

8
Dies ist überwiegend die ideale Lösung. Forking einen oder mehr externen Prozesse (zB awk, sed, tr, xargs) nur zu trimmen Leerzeichen aus einer einzigen Saite ist grundsätzlich verrückt - vor allem , wenn die meisten Schalen (einschließlich bash) bereits bietet nativen String munging Einrichtungen out-of-the-box.
Cecil Curry

81

Bash verfügt über eine Funktion namens Parametererweiterung , die unter anderem das Ersetzen von Zeichenfolgen anhand sogenannter Muster ermöglicht (Muster ähneln regulären Ausdrücken, es gibt jedoch grundlegende Unterschiede und Einschränkungen). [Flussences ursprüngliche Zeile: Bash hat reguläre Ausdrücke, aber sie sind gut versteckt:]

Im Folgenden wird gezeigt, wie Sie alle Leerzeichen (auch aus dem Innenraum) aus einem variablen Wert entfernen .

$ 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
Oder besser gesagt, es funktioniert für Leerzeichen in der Mitte eines Var, aber nicht, wenn ich versuche, es am Ende zu verankern.
Paul Tomblin

Hilft das jemandem? Aus der Manpage: "$ {parameter / pattern / string} [...] Wenn das Muster mit% beginnt, muss es am Ende des erweiterten Parameters übereinstimmen."

@Ant, also sind sie nicht wirklich reguläre Ausdrücke, aber etwas Ähnliches?
Paul Tomblin

3
Sie sind Regex, nur ein seltsamer Dialekt.

13
${var/ /}Entfernt das erste Leerzeichen. ${var// /}Entfernt alle Leerzeichen. Es gibt keine Möglichkeit, nur führende und nachfolgende Leerzeichen mit diesem Konstrukt zu kürzen.
Gilles 'SO - hör auf böse zu sein'

60

So entfernen Sie alle Leerzeichen am Anfang und am Ende einer Zeichenfolge (einschließlich Zeichen am Zeilenende):

echo $variable | xargs echo -n

Dadurch werden auch doppelte Leerzeichen entfernt:

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

Produziert: 'Diese Saite hat viele Leerzeichen'


5
Grundsätzlich entfernt das xargs alle Trennzeichen aus der Zeichenfolge. Standardmäßig wird das Leerzeichen als Trennzeichen verwendet (dies kann durch die Option -d geändert werden).
Rkachach

4
Dies ist bei weitem die sauberste (sowohl kurze als auch lesbare) Lösung.
Potherca

Warum brauchst du echo -nüberhaupt? echo " my string " | xargshat den gleichen Ausgang.
Bfontaine

echo -n entfernt auch das Zeilenende
rkachach

55

Entfernen Sie ein führendes und ein nachfolgendes Leerzeichen

trim()
{
    local trimmed="$1"

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

    echo "$trimmed"
}

Zum Beispiel:

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

Ausgabe:

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

Entfernen Sie alle führenden und nachfolgenden Leerzeichen

trim()
{
    local trimmed="$1"

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

    echo "$trimmed"
}

Zum Beispiel:

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

Ausgabe:

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

9
Dadurch wird nur 1 Leerzeichen abgeschnitten. Das Echo ergibt also'hello world ', 'foo bar', 'both sides '
Joe

@ Joe Ich habe eine bessere Option hinzugefügt.
Wjandrea

42

Aus dem Abschnitt Bash Guide zum Globbing

So verwenden Sie einen Extglob in einer Parametererweiterung

 #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  

Hier ist dieselbe Funktionalität, die in eine Funktion eingeschlossen ist (HINWEIS: Die an die Funktion übergebene Eingabezeichenfolge muss angegeben werden):

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

Verwendungszweck:

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

Wenn wir die Funktion ändern, die in einer Subshell ausgeführt werden soll, müssen wir uns nicht um die Prüfung der aktuellen Shell-Option für extglob kümmern, sondern können sie einfach festlegen, ohne die aktuelle Shell zu beeinflussen. Dies vereinfacht die Funktion enorm. Ich aktualisiere auch die Positionsparameter "an Ort und Stelle", damit ich nicht einmal eine lokale Variable benötige

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

damit:

$ 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
Wie Sie beobachtet haben, entfernt trim () nur führende und nachfolgende Leerzeichen.
GuruM

Wie mkelement bereits bemerkt hat, müssen Sie den Funktionsparameter als Zeichenfolge in Anführungszeichen übergeben, dh $ (trim "$ string") anstelle von $ (trim $ string). Ich habe den Code aktualisiert, um die korrekte Verwendung anzuzeigen. Vielen Dank.
GuruM

So sehr ich es auch schätze, über die Shell-Optionen Bescheid zu wissen, ich denke nicht, dass das Endergebnis eleganter ist, als einfach zwei Musterersetzungen
durchzuführen

Beachten Sie, dass Sie (mit einer ausreichend aktuellen Version von Bash?) Den Mechanismus zum Wiederherstellen der Option vereinfachen können extglob, indem Sie Folgendes verwenden shopt -p: Schreiben Sie einfach local restore="$(shopt -p extglob)" ; shopt -s extglobam Anfang Ihrer Funktion und eval "$restore"am Ende (außer, selbstverständlich, eval ist böse…).
Maëlan

Tolle Lösung! Eine mögliche Verbesserung: es sieht aus wie [[:space:]]könnte ersetzt werden, na ja, einen Raum: ${var##+( )}und ${var%%+( )}Arbeit als gut und sie sind leichter zu lesen.
DKroot

40

Sie können einfach trimmen mit echo:

foo=" qsdqsd qsdqs q qs   "

# Not trimmed
echo \'$foo\'

# Trim
foo=`echo $foo`

# Trimmed
echo \'$foo\'

Dadurch werden mehrere zusammenhängende Räume zu einem zusammengefasst.
Evgeni Sergeev

7
Haben Sie es versucht, wenn es fooeinen Platzhalter enthält? zB foo=" I * have a wild card"... Überraschung! Darüber hinaus werden dadurch mehrere zusammenhängende Räume zu einem zusammengefasst.
gniourf_gniourf

5
Dies ist eine hervorragende Lösung, wenn Sie: 1. keine Leerzeichen an den Enden möchten 2. nur ein Leerzeichen zwischen jedem Wort möchten 3. mit einer kontrollierten Eingabe ohne Platzhalter arbeiten. Es verwandelt im Wesentlichen eine schlecht formatierte Liste in eine gute.
musicin3d

Gute Erinnerung an die Platzhalter @gniourf_gniourf +1. Immer noch eine hervorragende Lösung, Vamp. +1 auch für dich.
Dr. Beco

25

Ich habe es immer mit sed gemacht

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

Wenn es eine elegantere Lösung gibt, hoffe ich, dass jemand sie veröffentlicht.


Könnten Sie die Syntax für erklären sed?
Farid99

2
Der reguläre Ausdruck entspricht allen nachgestellten Leerzeichen und ersetzt ihn durch nichts.
Paul Tomblin

4
Wie wäre es mit führenden Leerzeichen?
Qian Chen

Dadurch werden alle nachgestellten Leerzeichen entfernt sed -e 's/\s*$//'. Erläuterung: 's' bedeutet Suche, das '\ s' bedeutet alle Leerzeichen, das '*' bedeutet null oder viele, das '$' bedeutet bis zum Ende der Zeile und '//' bedeutet, alle Übereinstimmungen durch eine leere Zeichenfolge zu ersetzen .
Craig

Warum stehen in 's / * $ //' 2 Leerzeichen vor dem Sternchen anstelle eines einzelnen Leerzeichens? Ist das ein Tippfehler?
Brent212

24

Sie können Zeilenumbrüche löschen mit tr:

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

8
Ich möchte '\ n' nicht aus der Mitte der Zeichenfolge entfernen, sondern nur vom Anfang oder Ende.
zu viel PHP

24

Wenn die erweiterten Mustervergleichsfunktionen von Bash aktiviert sind ( shopt -s extglob), können Sie Folgendes verwenden:

{trimmed##*( )}

um eine beliebige Anzahl von führenden Leerzeichen zu entfernen.


Umwerfend! Ich denke, dies ist die leichteste und eleganteste Lösung.
Dubiousjim

1
In @ GuruMs Beitrag unten finden Sie eine ähnliche, aber allgemeinere Lösung, die (a) alle Formen von Leerzeichen behandelt und (b) auch nachgestellte Leerzeichen behandelt.
mklement0

@mkelement +1 für die Mühe, mein Code-Snippet als Funktion neu zu schreiben. Danke
GuruM

Funktioniert auch mit OpenBSDs Standard / bin / ksh. /bin/sh -o posixfunktioniert auch, aber ich bin misstrauisch.
Clint Pachl

Kein Bash-Assistent hier; was ist trimmed? Ist es eine eingebaute Sache oder die Variable, die getrimmt wird?
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
Tolle! Einfach und effektiv! Ganz klar meine Lieblingslösung. Vielen Dank!
Xebeche

1
@CraigMcQueen es ist der Variablenwert, wie readbei Variable unter seinem Namen $ 1 eine getrimmte Version seines Wertes $ {! 1}
Aquarius Power

2
Der Parameter der Funktion trim () ist ein Variablenname: Siehe den Aufruf von trim () in test_trim (). Innerhalb von trim (), wie von test_trim () aufgerufen, wird $ 1 zu foo und $ {! 1} zu $ ​​foo (dh zum aktuellen Inhalt der Variablen foo) erweitert. Durchsuchen Sie das Bash-Handbuch nach 'Variable Indirection'.
Flabdablet

1
Wie wäre es mit dieser kleinen Modifikation, um das Trimmen mehrerer Vars in einem Aufruf zu unterstützen? trim() { while [[ $# -gt 0 ]]; do read -rd '' $1 <<<"${!1}"; shift; done; }
Gene Pavlovsky

2
@AquariusPower Für die Einzeiler-Version muss kein Echo in einer Subshell verwendet read -rd '' str <<<"$str"werden.
Flabdablet

12

Es gibt viele Antworten, aber ich glaube immer noch, dass mein gerade geschriebenes Skript erwähnenswert ist, weil:

  • Es wurde erfolgreich in der Shell Bash / Dash / Busybox Shell getestet
  • es ist extrem klein
  • es hängt nicht von externen Befehlen ab und muss nicht gegabelt werden (-> schnelle und geringe Ressourcennutzung)
  • es funktioniert wie erwartet:
    • Es entfernt alle Leerzeichen und Tabulatoren von Anfang und Ende, aber nicht mehr
    • wichtig: Es wird nichts aus der Mitte der Zeichenfolge entfernt (viele andere Antworten tun dies), auch Zeilenumbrüche bleiben erhalten
    • Spezial: Die "$*"Verknüpfung mehrerer Argumente mit einem Leerzeichen. Wenn Sie nur das erste Argument zuschneiden und ausgeben möchten, verwenden Sie "$1"stattdessen
    • Wenn es keine Probleme mit übereinstimmenden Dateinamenmustern usw. gibt

Das Skript:

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

Verwendungszweck:

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

Ausgabe:

>here     is
    something<

Bah in C wäre dies viel einfacher zu implementieren!
Nils

Sicher. Leider ist dies nicht C und manchmal möchten Sie vermeiden, externe Tools aufzurufen
Daniel Alder

Um den Code sowohl lesbarer als auch kompatibler zu machen, können Sie die Klammern in [\ \t]
maskierte

@leondepeon hast du das versucht? Ich habe es versucht, als ich es geschrieben und erneut versucht habe, und Ihr Vorschlag funktioniert in keiner von Bash, Dash, Busybox
Daniel Alder

@ DanielAlder Ich habe es getan, aber da es bereits 3 Jahre her ist, kann ich den Code, in dem ich ihn verwendet habe, nicht finden. Jetzt würde ich wahrscheinlich [[:space:]]wie in einer der anderen Antworten verwenden: stackoverflow.com/a/3352015/3968618
leondepeon

11

Sie können Old-School verwenden tr. Dies gibt beispielsweise die Anzahl der geänderten Dateien in einem Git-Repository zurück, wobei Leerzeichen entfernt wurden.

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

1
Dadurch werden Leerzeichen nicht von vorne und hinten abgeschnitten, sondern alle Leerzeichen aus der Zeichenfolge entfernt.
Nick

11

Das hat bei mir funktioniert:

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>

Um das für das gleiche Ergebnis in weniger Zeilen zu setzen:

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

1
Hat bei mir nicht funktioniert. Der erste druckte eine unbeschnittene Zeichenfolge. Der zweite warf schlechte Substitution. Können Sie erklären, was hier los ist?
musicin3d

1
@ musicin3d: Dies ist eine Site, die ich häufig benutze und die beschreibt, wie die Manipulation von Variablen bei der Bash- Suche ${var##Pattern}nach weiteren Details funktioniert . Außerdem werden auf dieser Website Bash-Muster erläutert . Die ##Mittel entfernen also das gegebene Muster von vorne und die %%Mittel entfernen das gegebene Muster von hinten. Der +( )Teil ist das Muster und bedeutet "ein oder mehrere Vorkommen eines Leerzeichens"
gMale

Komisch, es hat in der Eingabeaufforderung funktioniert, aber nicht nach dem Transponieren in die Bash-Skriptdatei.
Dr. Beco

seltsam. Ist es in beiden Fällen dieselbe Bash-Version?
gMale

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

ODER

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

ODER

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

ODER

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

ODER

Aufbauend auf Moskits Ausdruck ...

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

ODER

# 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

Ich habe gesehen, dass Skripte nur die Variablenzuweisung verwenden, um den Job zu erledigen:

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

Leerzeichen werden automatisch zusammengeführt und gekürzt. Man muss auf Schalenmetacharaktere achten (potentielles Injektionsrisiko).

Ich würde auch empfehlen, Variablensubstitutionen in Shell-Bedingungen immer doppelt in Anführungszeichen zu setzen:

if [ -n "$var" ]; then

da so etwas wie ein -o oder ein anderer Inhalt in der Variablen Ihre Testargumente ändern könnte.


3
Es ist die nicht zitierte Verwendung von, $xyzmit echoder das Leerzeichen verschmilzt, nicht die Variablenzuweisung. Um den zugeschnittenen Wert in der Variablen in Ihrem Beispiel zu speichern, müssten Sie verwenden xyz=$(echo -n $xyz). Dieser Ansatz unterliegt auch einer potenziell unerwünschten Pfadnamenerweiterung (Globbing).
mklement0

Dies ist nur falsch, der Wert in der xyzVariablen wird NICHT getrimmt.
Caesarsol

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

1
Das funktioniert nicht, wenn zwischen zwei Wörtern mehr als ein Leerzeichen steht. Versuchen Sie: echo $(echo "1 2 3")(mit zwei Leerzeichen zwischen 1, 2 und 3).
Joshlf

7

Ich würde einfach sed verwenden:

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

a) Anwendungsbeispiel für einzeilige Zeichenfolgen

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

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

Ausgabe:

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

b) Anwendungsbeispiel für mehrzeilige Zeichenfolgen

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

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

Ausgabe:

GIVEN STRING: |    wordAA
   >wordB<
wordC    |

TRIMMED STRING: |wordAA
   >wordB<
wordC|

c) Schlussbemerkung:
Wenn Sie keine Funktion verwenden möchten, können Sie für einzeilige Zeichenfolgen einfach einen Befehl verwenden, der leichter zu merken ist:

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

Beispiel:

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

Ausgabe:

wordA wordB wordC

Die Verwendung der obigen Anweisungen für mehrzeilige Zeichenfolgen funktioniert ebenfalls , aber bitte beachten Sie, dass dadurch auch alle nachfolgenden / führenden internen Mehrfachbereiche abgeschnitten werden, wie GuruM in den Kommentaren bemerkt hat

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

Ausgabe:

wordAA
>four spaces before<
>one space before<

Wenn es Ihnen etwas ausmacht, diese Leerzeichen beizubehalten, verwenden Sie bitte die Funktion am Anfang meiner Antwort!

d) ERLÄUTERUNG der sed-Syntax "find and replace" für mehrzeilige Zeichenfolgen, die innerhalb der Funktionsanpassung verwendet werden:

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

Hinweis: Wie von @mkelement vorgeschlagen, funktioniert es nicht für mehrzeilige Zeichenfolgen, obwohl es für einzeilige Zeichenfolgen funktionieren sollte.
GuruM

1
Sie liegen falsch: Es funktioniert auch mit mehrzeiligen Zeichenfolgen.
Probieren Sie

+1 für die Verwendung - machte es mir einfach, den Code zu testen. Der Code funktioniert jedoch immer noch nicht für mehrzeilige Zeichenfolgen. Wenn Sie sich die Ausgabe genau ansehen, werden Sie feststellen, dass alle führenden / nachfolgenden internen Leerzeichen ebenfalls entfernt werden, z. B. wird das Leerzeichen vor "mehrzeilig" durch "mehrzeilig" ersetzt. Versuchen Sie einfach, die Anzahl der führenden / nachfolgenden Leerzeichen in jeder Zeile zu erhöhen.
GuruM

Jetzt verstehe ich was du meinst! Vielen Dank für das Head-up, ich habe meine Antwort bearbeitet.
Luca Borrione

@ "Luca Borrione" - willkommen :-) Würden Sie die sed-Syntax erklären, die Sie in trim () verwenden? Es kann auch jedem Benutzer Ihres Codes helfen, ihn für andere Zwecke zu optimieren. Es könnte auch helfen, Randfälle für den regulären Ausdruck zu finden.
GuruM

6

Hier ist eine trim () -Funktion, die Leerzeichen trimmt und normalisiert

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

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

Und eine andere Variante, die reguläre Ausdrücke verwendet.

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

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

Der erste Ansatz ist insofern schwierig, als er nicht nur das innere Leerzeichen normalisiert (ersetzt alle inneren Leerzeichenbereiche durch jeweils ein einzelnes Leerzeichen), sondern auch dem Globbing (Pfadnamenerweiterung) unterliegt, sodass beispielsweise ein *Zeichen in der Eingabezeichenfolge verwendet wird Erweitern Sie auf alle Dateien und Ordner im aktuellen Arbeitsordner. Wenn $ IFS auf einen nicht standardmäßigen Wert eingestellt ist, funktioniert das Trimmen möglicherweise nicht (obwohl dies durch Hinzufügen leicht behoben werden kann local IFS=$' \t\n'). Das Zuschneiden ist auf die folgenden Formen von Leerzeichen beschränkt: Leerzeichen \tund \nZeichen.
mklement0

1
Der zweite, auf regulären Ausdrücken basierende Ansatz ist großartig und nebenwirkungsfrei, aber in seiner gegenwärtigen Form problematisch: (a) In Bash v3.2 + funktioniert der Abgleich standardmäßig NICHT, da der reguläre Ausdruck in der angegebenen Reihenfolge UNquotiert sein muss zu arbeiten und (b) der reguläre Ausdruck selbst behandelt nicht den Fall, in dem die Eingabezeichenfolge ein einzelnes Nicht-Leerzeichen ist, das von Leerzeichen umgeben ist. Um diese Probleme zu beheben, ersetzen Sie die ifLeitung durch : if [[ "$trimmed" =~ ' '*([^ ]|[^ ].*[^ ])' '* ]]. Schließlich befasst sich der Ansatz nur mit Leerzeichen, nicht mit anderen Formen von Leerzeichen (siehe meinen nächsten Kommentar).
mklement0

2
Die Funktion, die reguläre Ausdrücke verwendet, behandelt nur Leerzeichen und keine anderen Formen von Leerzeichen, ist jedoch leicht zu verallgemeinern: Ersetzen Sie die ifZeile durch:[[ "$trimmed" =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]
mklement0

6

Verwenden Sie AWK:

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

Süß, das scheint zu funktionieren (ex :) $stripped_version=echo $ var | awk '{gsub (/ ^ + | + $ /, "")} 1'``
rogerdpack

4
außer dass awk nichts tut: Das Echo einer nicht zitierten Variablen hat bereits Leerzeichen entfernt
Glenn Jackman

6

Zuweisungen ignorieren führende und nachfolgende Leerzeichen und können als solche zum Trimmen verwendet werden:

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

8
Das ist nicht wahr. Es ist "Echo", das Leerzeichen entfernt, nicht die Zuweisung. Führen Sie in Ihrem Beispiel echo "$var"den Wert mit Leerzeichen aus.
Nicholas Sushkin

2
@NicholasSushkin Man könnte es tun, var=$(echo $var)aber ich empfehle es nicht. Andere hier vorgestellte Lösungen sind bevorzugt.
Xebeche

5

Dies hat nicht das Problem mit unerwünschtem Globbing, außerdem ist der innere Leerraum unverändert (vorausgesetzt, er $IFSist auf den Standardwert eingestellt)' \t\n' ).

Es liest bis zur ersten neuen Zeile (und enthält sie nicht) oder bis zum Ende der Zeichenfolge, je nachdem, was zuerst eintritt, und entfernt jede Mischung aus führenden und nachfolgenden Leerzeichen und \tZeichen. Wenn Sie mehrere Zeilen beibehalten möchten (und auch führende und nachfolgende Zeilenumbrüche entfernen möchten), verwenden Sie read -r -d '' var << eofstattdessen. Beachten Sie jedoch, dass Ihre Eingabe \neofkurz zuvor abgeschnitten wird, wenn sie etwas enthält. (Andere Formen des weißen Raumes, nämlich \r, \fund \vwerden nicht entfernt werden , auch wenn Sie sie zu $ IFS hinzufügen.)

read -r var << eof
$var
eof


5

Dadurch werden alle Leerzeichen aus Ihrem String entfernt.

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

/Ersetzt das erste Vorkommen und //alle Vorkommen von Leerzeichen in der Zeichenfolge. Dh alle Leerzeichen werden ersetzt durch - nichts


4

Dies ist die einfachste Methode, die ich je gesehen habe. Es wird nur Bash verwendet, es sind nur wenige Zeilen, der reguläre Ausdruck ist einfach und es entspricht allen Formen von Leerzeichen:

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

Hier ist ein Beispielskript zum Testen:

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
Sicherlich vorzuziehen, zum Beispiel (ihr Götter!) Python zu beschießen. Außer ich denke, es ist einfacher und allgemeiner, Zeichenfolgen, die nur Leerzeichen enthalten, korrekt zu behandeln. Ein leicht vereinfachter Ausdruck wäre:^[[:space:]]*(.*[^[:space:]])?[[:space:]]*$
Ron Burk

4

Python hat eine Funktion strip(), die identisch mit PHPs funktioniert trim(), daher können wir nur ein wenig Inline-Python ausführen, um ein leicht verständliches Dienstprogramm dafür zu erstellen:

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

Dadurch werden führende und nachfolgende Leerzeichen (einschließlich Zeilenumbrüche) gekürzt.

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

Während dies funktioniert, sollten Sie eine Lösung anbieten, bei der kein vollständiger Python-Interpreter gestartet wird, nur um eine Zeichenfolge zu kürzen. Es ist nur verschwenderisch.
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

Ich stellte fest, dass ich Code aus einer unordentlichen sdiffAusgabe hinzufügen musste, um ihn zu bereinigen:

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'

Dadurch werden die nachgestellten Leerzeichen und andere unsichtbare Zeichen entfernt.


3

Leerzeichen in ein Leerzeichen entfernen:

(text) | fmt -su
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.