Come tagliare gli spazi bianchi da una variabile Bash?


922

Ho uno script di shell con questo codice:

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

Ma il codice condizionale viene sempre eseguito, perché hg ststampa sempre almeno un carattere di nuova riga.

  • C'è un modo semplice per eliminare gli spazi bianchi $var(come trim()in PHP )?

o

  • Esiste un modo standard di affrontare questo problema?

Potrei usare sed o AWK , ma mi piacerebbe pensare che ci sia una soluzione più elegante a questo problema.


3
Correlato, se si desidera tagliare lo spazio su un numero intero e ottenere l'intero, avvolgere con $ (($ var)) e può farlo anche all'interno di virgolette doppie. Questo è diventato importante quando ho usato la dichiarazione della data e con i nomi dei file.
Volomike,

"Esiste un modo standard di affrontare questo problema?" Sì, usa [[anziché [. $ var=$(echo) $ [ -n $var ]; echo $? #undesired test return 0 $ [[ -n $var ]]; echo $? 1
user.friendly

Se aiuta, almeno dove lo sto testando su Ubuntu 16.04. Utilizzando i seguenti partite assetto in tutti i modi: echo " This is a string of char " | xargs. Se invece avete una sola citazione nel testo è possibile effettuare le seguenti operazioni: echo " This i's a string of char " | xargs -0. Nota che menziono l'ultimo di xargs (4.6.0)
Luis Alvarado,

La condizione non è vera a causa di una nuova riga poiché i backtick ingoiano l'ultima riga. Questo non stamperà nulla test=`echo`; if [ -n "$test" ]; then echo "Not empty"; fi, questo comunque lo farà test=`echo "a"`; if [ -n "$test" ]; then echo "Not empty"; fi- quindi alla fine ci deve essere più di una semplice riga.
Mecki,

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

Risposte:


1022

Definiamo una variabile contenente spazi bianchi iniziali, finali e intermedi:

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

Come rimuovere tutti gli spazi bianchi (indicato da [: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

Come rimuovere solo i principali spazi bianchi:

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

Come rimuovere solo gli spazi vuoti finali:

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

Come rimuovere gli spazi iniziali e finali - concatenare la 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

In alternativa, se bash lo supporta, puoi sostituirlo echo -e "${FOO}" | sed ...con sed ... <<<${FOO}, in questo modo (per gli spazi bianchi finali):

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

63
Per generalizzare la soluzione per gestire tutte le forme di spazio bianco, sostituire il carattere spazio nei comandi tre sedcon [[:space:]]. Si noti che l' sedapproccio funziona solo su input a riga singola . Per gli approcci che funzionano con input multilinea e utilizzano anche le funzionalità integrate di bash, vedere le risposte di @bashfu e @GuruM. Una versione in linea generalizzata della soluzione di @Nicholas Sushkin sarebbe simile a questa: trimmed=$([[ " test test test " =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]; echo -n "${BASH_REMATCH[1]}")
mklement0

7
Se lo fai spesso, accodandoti alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'"al tuo ~/.profileti permette di usare echo $SOMEVAR | trime cat somefile | trim.
istanza di me

Ho scritto una sedsoluzione che utilizza solo una singola espressione anziché due: sed -r 's/^\s*(\S+(\s+\S+)*)\s*$/\1/'. Taglia gli spazi bianchi iniziali e finali e cattura qualsiasi sequenza separata da spazi bianchi di caratteri non bianchi nel mezzo. Godere!
Victor Zamanian,

@VictorZamanian La tua soluzione non funziona se l'input contiene solo spazi bianchi. Le soluzioni sed a due modelli fornite da MattyV e istanza di me funzionano perfettamente con input solo di spazi bianchi.
Torben,

@Torben Fair point. Suppongo che la singola espressione possa essere subordinata, in |modo da mantenerla come una singola espressione, piuttosto che diverse.
Victor Zamanian,

966

Una semplice risposta è:

echo "   lol  " | xargs

Xargs farà il taglio per te. È un comando / programma, nessun parametro, restituisce la stringa tagliata, facile!

Nota: questo non rimuove tutti gli spazi interni, quindi "foo bar"rimane lo stesso; non diventa "foobar". Tuttavia, più spazi saranno condensati in singoli spazi, così "foo bar"diventerà "foo bar". Inoltre, non rimuove i caratteri di fine riga.


27
Bello. Funziona davvero bene. Ho deciso di convogliarlo xargs echosolo per essere prolisso su quello che sto facendo, ma xargs da solo userà l'eco per impostazione predefinita.
Sarà il

24
Un bel trucco, ma fai attenzione, puoi usarlo per una stringa di una riga ma - per il disegno di xargs - non si limiterà a tagliare con contenuto in più righe. sed è tuo amico allora.
Jocelyn delalande,

22
L'unico problema con xargs è che introdurrà una nuova riga, se si desidera mantenere la nuova riga off lo consiglierei sed 's/ *$//'in alternativa. Si può vedere il xargsritorno a capo in questo modo: echo -n "hey thiss " | xargs | hexdump si noterà 0a73il aè il ritorno a capo. Se fai lo stesso con sed: echo -n "hey thiss " | sed 's/ *$//' | hexdumpvedrai 0073, nessuna nuova riga.

8
un'attenta; questo si interromperà se la stringa di xargs contiene spazi in eccesso in mezzo. Come "questo è un argomento". xargs si dividerà in quattro.
bos

64
Questo non va bene. 1. Si trasformerà a<space><space>bin a<space>b. 2. Ancora di più: si trasformerà a"b"c'd'ein abcde. 3. Ancora di più: fallirà a"b, ecc.
Sasha,

359

Esiste una soluzione che utilizza solo i built-in di Bash chiamati caratteri jolly :

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

Ecco lo stesso avvolto in una funzione:

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

Si passa la stringa da tagliare in forma tra virgolette. per esempio:

trim "   abc   "

Una cosa bella di questa soluzione è che funzionerà con qualsiasi shell conforme a POSIX.

Riferimento


18
Intelligente! Questa è la mia soluzione preferita in quanto utilizza la funzionalità bash integrata. Grazie per la pubblicazione! @San, sono due tagli di stringa nidificati. Ad esempio, s=" 1 2 3 "; echo \""${s%1 2 3 }"\"eliminerebbe tutto dalla fine, restituendo il comando " ". Subbing 1 2 3 con [![:space:]]*dice di "trovare il primo personaggio non spaziale, quindi occultarlo e tutto il resto". L'utilizzo %%invece di %rendere avida l'operazione di rifinitura. Questo è annidato in un trim-greedy non avido dall'inizio, quindi in effetti si esegue " "il trim dall'inizio. Quindi, scambia%, # e * per gli spazi finali. Bam!
Marco G.

2
Non ho trovato effetti collaterali indesiderati e il codice principale funziona anche con altre shell simili a POSIX. Tuttavia, con Solaris 10, non funziona /bin/sh(solo con /usr/xpg4/bin/sh, ma questo non è ciò che verrà utilizzato con i normali script sh).
vinc17,

9
Soluzione molto migliore rispetto all'uso di sed, tr ecc., Poiché è molto più veloce, evitando qualsiasi fork (). Su Cygwin la differenza di velocità è ordini di grandezza.
Gene Pavlovsky,

9
@San All'inizio ero sconcertato perché pensavo che fossero espressioni regolari. Non sono. Piuttosto, questa è la sintassi di Pattern Matching ( gnu.org/software/bash/manual/html_node/Pattern-Matching.html , wiki.bash-hackers.org/syntax/pattern ) utilizzata nella rimozione del sottostring ( tldp.org/LDP/abs /html/string-manipulation.html ). Così ${var%%[![:space:]]*}dice "rimuovi dalla varsottostringa più lunga che inizia con un carattere non spaziale". Ciò significa che ti rimangono solo gli spazi iniziali, che successivamente rimuovi ${var#... La seguente riga (finale) è l'opposto.
Ohad Schneider,

8
Questa è la soluzione ideale per la stragrande maggioranza. Fork uno o più processi esterni (ad esempio, awk, sed, tr, xargs) solo per spazi assetto da una singola stringa è fondamentalmente folle - particolare quando molte shell (compresi bash) già fornire stringa nativa munging strutture out-of-the-box.
Cecil Curry,

81

Bash ha una funzione chiamata espansione dei parametri , che, tra le altre cose, consente la sostituzione delle stringhe in base ai cosiddetti pattern (i pattern assomigliano alle espressioni regolari, ma ci sono differenze e limitazioni fondamentali). [linea originale di flussence: Bash ha espressioni regolari, ma sono ben nascoste:]

Di seguito viene illustrato come rimuovere tutto lo spazio bianco (anche dall'interno) da un valore variabile.

$ 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
O meglio, funziona per gli spazi nel mezzo di un var, ma non quando provo ad ancorarlo alla fine.
Paul Tomblin,

Questo aiuta? Dalla manpage: "$ {parametro / modello / stringa} [...] Se il modello inizia con%, deve corrispondere alla fine del valore espanso del parametro."

@Ant, quindi non sono espressioni davvero regolari, ma qualcosa di simile?
Paul Tomblin,

3
Sono regex, solo uno strano dialetto.

13
${var/ /}rimuove il primo carattere spaziale. ${var// /}rimuove tutti i caratteri dello spazio. Non c'è modo di tagliare solo gli spazi bianchi iniziali e finali solo con questo costrutto.
Gilles 'SO- smetti di essere malvagio' il

60

Per rimuovere tutti gli spazi dall'inizio e dalla fine di una stringa (inclusi i caratteri di fine riga):

echo $variable | xargs echo -n

Ciò rimuoverà anche gli spazi duplicati:

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

Produce: "questa stringa ha molti spazi"


5
Fondamentalmente gli xarg rimuovono tutti i delimitatori dalla stringa. Di default usa lo spazio come delimitatore (questo potrebbe essere modificato dall'opzione -d).
rkachach

4
Questa è di gran lunga la soluzione più pulita (sia breve che leggibile).
Potherca,

Perché ne hai bisogno echo -n? echo " my string " | xargsha lo stesso output.
bfontaine,

echo -n rimuove anche la fine della riga
rkachach

55

Striscia uno spazio iniziale e uno finale

trim()
{
    local trimmed="$1"

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

    echo "$trimmed"
}

Per esempio:

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

Produzione:

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

Striscia tutti gli spazi iniziali e finali

trim()
{
    local trimmed="$1"

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

    echo "$trimmed"
}

Per esempio:

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

Produzione:

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

9
Questo taglierà solo 1 carattere spazio. Quindi l'eco si traduce in'hello world ', 'foo bar', 'both sides '
Joe,

@Joe ho aggiunto un'opzione migliore.
wjandrea,

42

Dalla sezione Bash Guide su globbing

Per utilizzare un extglob in un'espansione dei parametri

 #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  

Ecco la stessa funzionalità racchiusa in una funzione (NOTA: è necessario citare la stringa di input passata alla funzione):

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

Uso:

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

Se alteriamo la funzione da eseguire in una subshell, non dobbiamo preoccuparci di esaminare l'opzione di shell corrente per extglob, possiamo semplicemente impostarla senza influire sulla shell corrente. Questo semplifica enormemente la funzione. Inoltre aggiorno i parametri posizionali "sul posto", quindi non ho nemmeno bisogno di una variabile locale

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

così:

$ 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
come hai osservato trim () rimuove solo gli spazi bianchi iniziali e finali.
GuruM,

Come mkelement ha già notato, è necessario passare il parametro della funzione come stringa tra virgolette, ad esempio $ (trim "$ string") anziché $ (trim $ string). Ho aggiornato il codice per mostrare l'utilizzo corretto. Grazie.
GuruM,

Per quanto apprezzo conoscere le opzioni della shell, non penso che il risultato finale sia più elegante del semplice fare 2 sostituzioni di pattern
vedi

Nota che (con una versione abbastanza recente di Bash?), Puoi semplificare il meccanismo per ripristinare l'opzione extglob, usando shopt -p: scrivi semplicemente local restore="$(shopt -p extglob)" ; shopt -s extgloball'inizio della tua funzione e eval "$restore"alla fine (tranne, concesso, eval è male ...).
Maëlan,

Ottima soluzione! Un potenziale miglioramento: sembra che [[:space:]]potrebbe essere sostituito con uno spazio: ${var##+( )}e ${var%%+( )}funziona anche e sono più facili da leggere.
DKroot

40

Puoi tagliare semplicemente con echo:

foo=" qsdqsd qsdqs q qs   "

# Not trimmed
echo \'$foo\'

# Trim
foo=`echo $foo`

# Trimmed
echo \'$foo\'

Questo comprime più spazi contigui in uno.
Evgeni Sergeev,

7
Hai provato quando foocontiene un carattere jolly? ad esempio, foo=" I * have a wild card"... sorpresa! Inoltre questo collassa diversi spazi contigui in uno.
gniourf_gniourf,

5
Questa è un'ottima soluzione se: 1. non vuoi spazi alle estremità 2. vuoi solo uno spazio tra ogni parola 3. stai lavorando con un input controllato senza caratteri jolly. Trasforma essenzialmente un elenco formattato in modo errato in un elenco valido.
musicin3d

Buon promemoria dei caratteri jolly @gniourf_gniourf +1. Ottima soluzione, Vamp. +1 anche a te.
Dr Beco,

25

L'ho sempre fatto con sed

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

Se esiste una soluzione più elegante, spero che qualcuno la pubblichi.


potresti spiegare la sintassi per sed?
farid99,

2
L'espressione regolare corrisponde a tutti gli spazi bianchi finali e lo sostituisce con nulla.
Paul Tomblin,

4
Che ne dici di condurre spazi bianchi?
Qian Chen,

Questo elimina tutti gli spazi bianchi finali sed -e 's/\s*$//'. Spiegazione: 's' significa ricerca, '\ s' significa tutti gli spazi bianchi, '*' significa zero o molti, '$' significa fino alla fine della riga e '//' significa sostituisce tutte le corrispondenze con una stringa vuota .
Craig,

In 's / * $ //', perché ci sono 2 spazi prima dell'asterisco, anziché un singolo spazio? È un errore di battitura?
Brent212

24

Puoi eliminare le nuove righe con tr:

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

8
Non voglio rimuovere '\ n' dal centro della stringa, solo dall'inizio o dalla fine.
troppo php il

24

Con le funzionalità di corrispondenza del pattern esteso di Bash abilitate ( shopt -s extglob), puoi usare questo:

{trimmed##*( )}

per rimuovere una quantità arbitraria di spazi iniziali.


Eccezionale! Penso che questa sia la soluzione più leggera ed elegante.
dubiousjim,

1
Vedi il post di @ GuruM di seguito per una soluzione simile, ma più generica che (a) si occupa di tutte le forme di spazio bianco e (b) gestisce anche lo spazio bianco finale .
mklement0

@mkelement +1 per il problema di riscrivere il mio frammento di codice come funzione. Grazie
GuruM,

Funziona anche con il valore predefinito / bin / ksh di OpenBSD. /bin/sh -o posixfunziona anche ma sono sospettoso.
Clint Pachl,

Non un mago bash qui; cosa trimmed? È una cosa incorporata o la variabile che viene ritagliata?
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
Sorprendente! Semplice ed efficace! Chiaramente la mia soluzione preferita. Grazie!
xebeche,

1
@CraigMcQueen è il valore della variabile, poiché readmemorizzerà a variabile con il nome $ 1 una versione ridotta del suo valore $ {! 1}
Aquarius Power il

2
Il parametro della funzione trim () è un nome variabile: vedere la chiamata a trim () all'interno di test_trim (). All'interno di trim () come chiamato da test_trim (), $ 1 si espande in foo e $ {! 1} si espande in $ foo (cioè nel contenuto corrente della variabile foo). Cerca nel manuale di bash "indiretta variabile".
flabdablet,

1
Che ne dici di questa piccola modifica, per supportare il taglio di più variabili in una chiamata? trim() { while [[ $# -gt 0 ]]; do read -rd '' $1 <<<"${!1}"; shift; done; }
Gene Pavlovsky,

2
@AquariusPower non è necessario utilizzare l'eco in una subshell per la versione one-liner, basta read -rd '' str <<<"$str".
flabdablet,

12

Ci sono molte risposte, ma credo ancora che la mia sceneggiatura appena scritta meriti di essere citata perché:

  • è stato testato con successo nella shell shell bash / dash / busybox
  • è estremamente piccolo
  • non dipende da comandi esterni e non necessita di fork (-> utilizzo rapido e basso delle risorse)
  • funziona come previsto:
    • rimuove tutti gli spazi e le schede dall'inizio e dalla fine, ma non di più
    • importante: non rimuove nulla dal centro della stringa (molte altre risposte lo fanno), anche le nuove righe rimarranno
    • speciale: "$*"unisce più argomenti usando uno spazio. se vuoi tagliare e produrre solo il primo argomento, usa "$1"invece
    • se non ha problemi con la corrispondenza dei modelli di nomi di file ecc

Il copione:

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

Uso:

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

Produzione:

>here     is
    something<

Bah in C questo sarebbe molto più semplice da implementare!
Nils,

Sicuro. Sfortunatamente, questo non è C e talvolta vuoi evitare di chiamare strumenti esterni
Daniel Alder,

Per rendere il codice sia più leggibile che compatibile con copia e [\ \t]
incolla

@leondepeon hai provato questo? Ho provato quando l'ho scritto e riprovato, e il tuo suggerimento non funziona in nessuna di bash, dash, busybox
Daniel Alder,

@DanielAlder l'ho fatto, ma come è già stato 3 anni fa, non riesco a trovare il codice dove l'ho usato. Ora però, probabilmente sarei uso [[:space:]]come in una delle altre risposte: stackoverflow.com/a/3352015/3968618
leondepeon

11

Puoi usare la vecchia scuola tr. Ad esempio, questo restituisce il numero di file modificati in un repository git, spazi bianchi eliminati.

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

1
Questo non taglia gli spazi bianchi dalla parte anteriore e posteriore, ma rimuove tutti gli spazi bianchi dalla stringa.
Nick,

11

Questo ha funzionato per me:

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>

Per dirlo su un numero inferiore di righe per lo stesso risultato:

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

1
Non ha funzionato per me. Il primo ha stampato una stringa non tagliata. Il secondo ha gettato una brutta sostituzione. Puoi spiegare cosa sta succedendo qui?
musicin3d

1
@ musicin3d: questo è un sito che uso frequentemente che spiega come funziona la manipolazione variabile nella ricerca bash${var##Pattern} per maggiori dettagli. Inoltre, questo sito spiega i modelli bash . Quindi i ##mezzi rimuovono il motivo dato dalla parte anteriore e %%mezzi rimuovono il motivo dato dalla parte posteriore. La +( )porzione è il modello e significa "una o più occorrenze di uno spazio"
gMale

Divertente, ha funzionato nel prompt, ma non dopo averlo trasposto nel file di script bash.
Dr Beco,

strano. È la stessa versione bash in entrambi i casi?
gMale

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

O

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

O

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

O

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

O

Costruire sull'espressione soul di moskit ...

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

O

# 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

Ho visto degli script usare solo l'assegnazione delle variabili per fare il lavoro:

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

Lo spazio bianco viene automaticamente coalizzato e tagliato. Bisogna stare attenti ai metacaratteri della shell (potenziale rischio di iniezione).

Vorrei anche raccomandare sempre le doppie sostituzioni di variabili nei condizionali di shell:

if [ -n "$var" ]; then

poiché qualcosa come un -o o altro contenuto nella variabile potrebbe modificare gli argomenti del test.


3
È l'uso non quotato di $xyzcon echoche fa fondere gli spazi bianchi, non l'assegnazione variabile. Per memorizzare il valore tagliato nella variabile nel tuo esempio, dovresti usare xyz=$(echo -n $xyz). Inoltre, questo approccio è soggetto all'espansione del percorso potenzialmente indesiderato (globbing).
mklement0

questo è solo sbagliato, il valore nella xyzvariabile NON è tagliato.
Caesarsol,

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

1
Non funzionerà se ci sono più di uno spazio tra due parole qualsiasi. Prova: echo $(echo "1 2 3")(con due spazi tra 1, 2 e 3).
joshlf

7

Vorrei semplicemente usare sed:

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

a) Esempio di utilizzo su stringa a riga singola

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

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

Produzione:

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

b) Esempio di utilizzo su stringa multilinea

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

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

Produzione:

GIVEN STRING: |    wordAA
   >wordB<
wordC    |

TRIMMED STRING: |wordAA
   >wordB<
wordC|

c) Nota finale:
se non ti piace usare una funzione, per una stringa a riga singola puoi semplicemente usare un comando "più facile da ricordare" come:

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

Esempio:

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

Produzione:

wordA wordB wordC

L'uso di quanto sopra sulle stringhe multilinea funzionerà anche , ma tieni presente che taglierà anche qualsiasi spazio multiplo interno iniziale / iniziale, come notato da GuruM nei commenti

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

Produzione:

wordAA
>four spaces before<
>one space before<

Quindi, se ti dispiace mantenere quegli spazi, per favore usa la funzione all'inizio della mia risposta!

d) SPIEGAZIONE della sintassi sed "trova e sostituisci" sulle stringhe multilinea utilizzate all'interno del trim della funzione:

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

Nota: come suggerito da @mkelement, non funzionerà per le stringhe a più righe sebbene dovrebbe funzionare per le stringhe a riga singola.
GuruM,

1
Ti sbagli: funziona anche su stringhe multilinea.
Provalo

+1 per l'uso: mi ha reso facile testare il codice. Tuttavia, il codice non funzionerà ancora per le stringhe multilinea. Se osservi attentamente l'output, noterai che anche gli spazi interni iniziali / finali vengono rimossi, ad esempio lo spazio davanti a "multi-linea" viene sostituito da "multi-linea". Prova ad aumentare il numero di spazi iniziali / finali su ciascuna riga.
GuruM,

Ora capisco cosa intendi! Grazie per il testa a testa, ho modificato la mia risposta.
Luca Borrione,

@ "Luca Borrione" - benvenuto :-) Spiegheresti la sintassi sed che stai usando in trim ()? Potrebbe anche aiutare qualsiasi utente del tuo codice a modificarlo ad altri usi. Inoltre potrebbe anche aiutare a trovare casi limite per l'espressione regolare.
GuruM,

6

Ecco una funzione trim () che taglia e normalizza gli spazi bianchi

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

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

E un'altra variante che utilizza espressioni regolari.

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

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

Il primo approccio è complicato in quanto non solo normalizza gli spazi bianchi interni (sostituisce tutti gli spazi interni degli spazi bianchi con un singolo spazio ciascuno), ma è anche soggetto a globbing (espansione del percorso) in modo che, ad esempio, un *carattere nella stringa di input espandi a tutti i file e le cartelle nella cartella di lavoro corrente. Infine, se $ IFS è impostato su un valore non predefinito, il taglio potrebbe non funzionare (anche se è possibile rimediare facilmente aggiungendo local IFS=$' \t\n'). Il taglio è limitato alle seguenti forme di spazio bianco: spazi \te \ncaratteri.
mklement0

1
Il secondo approccio basato sull'espressione regolare è eccezionale e privo di effetti collaterali, ma nella sua forma attuale è problematico: (a) su bash v3.2 +, la corrispondenza di default NON funzionerà, perché l'espressione regolare deve essere NON quotata in ordine lavorare e (b) l'espressione regolare stessa non gestisce il caso in cui la stringa di input è un singolo carattere non spaziale circondato da spazi. Per risolvere questi problemi, sostituire la iflinea con: if [[ "$trimmed" =~ ' '*([^ ]|[^ ].*[^ ])' '* ]]. Infine, l'approccio riguarda solo gli spazi, non altre forme di spazio bianco (vedi il mio prossimo commento).
mklement0

2
La funzione che utilizza espressioni regolari si occupa solo di spazi e non di altre forme di spazi bianchi, ma è facile generalizzare: sostituisci la iflinea con:[[ "$trimmed" =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]
mklement0

6

Usa AWK:

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

Dolce che sembra funzionare (ex :) $stripped_version=echo $ var | awk '{gsub (/ ^ + | + $ /, "")} 1'``
rogerdpack

4
tranne che awk non sta facendo nulla: l'eco di una variabile non quotata ha già eliminato gli spazi bianchi
glenn jackman,

6

Le assegnazioni ignorano gli spazi bianchi iniziali e finali e come tali possono essere utilizzate per tagliare:

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

8
Non è vero. È "eco" che rimuove gli spazi bianchi, non il compito. Nel tuo esempio, fai echo "$var"per vedere il valore con spazi.
Nicholas Sushkin,

2
@NicholasSushkin Uno potrebbe fare var=$(echo $var)ma non lo consiglio. Altre soluzioni qui presentate sono preferite.
xebeche,

5

Questo non ha il problema con il globbing indesiderato, inoltre, lo spazio bianco interno non è modificato (supponendo che $IFSsia impostato sul valore predefinito, che è ' \t\n').

Legge fino alla prima riga (e non la include) o alla fine della stringa, a seconda di quale delle due condizioni si verifichi per prima, e toglie qualsiasi mix di spazio e \tcaratteri iniziali e finali . Se si desidera conservare più righe (e anche rimuovere le righe iniziali e finali), utilizzare read -r -d '' var << eofinvece; nota, tuttavia, che se l'input contiene \neof, verrà tagliato appena prima. (Altre forme di spazio bianco, ovvero \r, \fe \v, non vengono rimosse, anche se le aggiungi a $ IFS.)

read -r var << eof
$var
eof


5

Ciò rimuoverà tutti gli spazi bianchi dalla stringa,

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

/sostituisce la prima occorrenza e //tutte le occorrenze di spazi bianchi nella stringa. Cioè tutti gli spazi bianchi vengono sostituiti da - niente


4

Questo è il metodo più semplice che abbia mai visto. Usa solo Bash, è solo poche righe, la regexp è semplice e si adatta a tutte le forme di spazio bianco:

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

Ecco uno script di esempio con cui testarlo:

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
Sicuramente preferibile, ad esempio (voi dèi!), Sborsare su Python. Tranne che penso che sia più semplice e più generale gestire correttamente la stringa che contiene solo spazi.L'espressione leggermente semplificata sarebbe:^[[:space:]]*(.*[^[:space:]])?[[:space:]]*$
Ron Burk

4

Python ha una funzione strip() che funziona in modo identico a PHP trim(), quindi possiamo solo fare un po 'di Python in linea per creare un'utilità facilmente comprensibile per questo:

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

Ciò eliminerà gli spazi bianchi iniziali e finali (comprese le nuove linee).

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

mentre funziona, potresti prendere in considerazione l'idea di offrire una soluzione che non implichi l'avvio di un interprete Python completo solo per tagliare una stringa. È solo uno spreco.
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

Ho scoperto che dovevo aggiungere del codice da un sdiffoutput disordinato per ripulirlo:

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'

Questo rimuove gli spazi finali e altri personaggi invisibili.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.