Comment découper un espace à partir d'une variable Bash?


923

J'ai un script shell avec ce code:

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

Mais le code conditionnel s'exécute toujours, car hg stimprime toujours au moins un caractère de nouvelle ligne.

  • Existe-t-il un moyen simple de supprimer les espaces $var(comme trim()en PHP )?

ou

  • Existe-t-il un moyen standard de traiter ce problème?

Je pourrais utiliser sed ou AWK , mais j'aimerais penser qu'il existe une solution plus élégante à ce problème.


3
En relation, si vous vouliez réduire l'espace sur un entier et simplement obtenir l'entier, encapsulez avec $ (($ var)), et pouvez même le faire lorsque vous êtes à l'intérieur de guillemets doubles. Cela est devenu important lorsque j'ai utilisé la déclaration de date et avec les noms de fichiers.
Volomike

"Existe-t-il un moyen standard de traiter ce problème?" Oui, utilisez [[au lieu de [. $ var=$(echo) $ [ -n $var ]; echo $? #undesired test return 0 $ [[ -n $var ]]; echo $? 1
user.friendly

Si cela aide, au moins où le teste-t-il sur Ubuntu 16.04. En utilisant les résultats suivants garniture dans tous les sens: echo " This is a string of char " | xargs. Si vous avez cependant une seule citation dans le texte que vous pouvez faire ce qui suit: echo " This i's a string of char " | xargs -0. Notez que je mentionne le dernier des xargs (4.6.0)
Luis Alvarado

La condition n'est pas vraie à cause d'un retour à la ligne car les retours en arrière avalent le dernier retour à la ligne. Cela n'imprimera rien test=`echo`; if [ -n "$test" ]; then echo "Not empty"; fi, mais cela le fera test=`echo "a"`; if [ -n "$test" ]; then echo "Not empty"; fi- il doit donc y avoir plus qu'une simple nouvelle ligne à la fin.
Mecki

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

Réponses:


1023

Définissons une variable contenant des espaces de début, de fin et intermédiaires:

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

Comment supprimer tous les espaces (indiqués par [: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

Comment supprimer uniquement les espaces principaux:

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

Comment supprimer uniquement les espaces de fin:

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

Comment supprimer les espaces de début et de fin - enchaînez les 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

Alternativement, si votre bash le prend en charge, vous pouvez le remplacer echo -e "${FOO}" | sed ...par sed ... <<<${FOO}, comme ceci (pour les espaces de fin):

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

63
Pour généraliser la solution pour gérer toutes les formes d'espaces, remplacez le caractère espace dans les commandes tret sedpar [[:space:]]. Notez que l' sedapproche ne fonctionnera que sur une entrée sur une seule ligne . Pour les approches qui fonctionnent avec une entrée multiligne et utilisent également les fonctionnalités intégrées de bash, consultez les réponses de @bashfu et @GuruM. Une version en ligne généralisée de la solution de @Nicholas Sushkin ressemblerait à ceci: trimmed=$([[ " test test test " =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]; echo -n "${BASH_REMATCH[1]}")
mklement0

7
Si vous le faites souvent, l'ajout alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'"à votre ~/.profilepermet d'utiliser echo $SOMEVAR | trimet cat somefile | trim.
instanceof me

J'ai écrit une sedsolution qui utilise une seule expression au lieu de deux: sed -r 's/^\s*(\S+(\s+\S+)*)\s*$/\1/'. Il coupe les espaces blancs de début et de fin et capture toutes les séquences séparées par des espaces de caractères non blancs au milieu. Prendre plaisir!
Victor Zamanian

@VictorZamanian Votre solution ne fonctionne pas si l'entrée ne contient que des espaces. Les solutions sed à deux modèles fournies par MattyV et instanceof me fonctionnent très bien avec une entrée d'espace blanc uniquement.
Torben

@Torben Fair point. Je suppose que l'expression unique pourrait être conditionnée, avec |, afin de la garder comme une seule expression, plutôt que plusieurs.
Victor Zamanian

966

Une réponse simple est:

echo "   lol  " | xargs

Xargs fera le réglage pour vous. C'est une commande / programme, pas de paramètres, renvoie la chaîne coupée, aussi simple que ça!

Remarque: cela ne supprime pas tous les espaces internes et "foo bar"reste donc le même; cela ne devient PAS "foobar". Cependant, plusieurs espaces seront condensés en espaces uniques, ainsi "foo bar"le deviendra "foo bar". De plus, il ne supprime pas les caractères de fin de ligne.


27
Agréable. Cela fonctionne vraiment bien. J'ai décidé de le rediriger xargs echojuste pour être bavard sur ce que je fais, mais xargs à lui seul utilisera echo par défaut.
Le

24
Belle astuce, mais attention, vous pouvez l'utiliser pour une chaîne d'une seule ligne mais −par la conception xargs - cela ne fera pas seulement le découpage avec du contenu canalisé sur plusieurs lignes. sed est votre ami alors.
Jocelyn delalande

22
Le seul problème avec xargs est qu'il introduira une nouvelle ligne, si vous voulez garder la nouvelle ligne désactivée, je recommanderais sed 's/ *$//'comme alternative. Vous pouvez voir la xargsnouvelle ligne comme ceci: echo -n "hey thiss " | xargs | hexdump vous remarquerez 0a73que ac'est la nouvelle ligne. Si vous faites de même avec sed: echo -n "hey thiss " | sed 's/ *$//' | hexdumpvous verrez 0073, pas de nouvelle ligne.

8
Prudent; cela cassera fort si la chaîne de xargs contient des espaces excédentaires entre les deux. Comme "c'est un argument". xargs se divisera en quatre.
bos

64
C'est mauvais. 1. Il se transformera a<space><space>ben a<space>b. 2. Plus encore: il se transformera a"b"c'd'een abcde. 3. Encore plus: il échouera a"b, etc.
Sasha

359

Il existe une solution qui n'utilise que des Bash intégrés appelés caractères génériques :

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

Voici le même enveloppé dans une fonction:

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

Vous passez la chaîne à couper sous forme de guillemets. par exemple:

trim "   abc   "

Une bonne chose à propos de cette solution est qu'elle fonctionnera avec n'importe quel shell compatible POSIX.

Référence


18
Intelligent! C'est ma solution préférée car elle utilise la fonctionnalité bash intégrée. Merci d'avoir posté! @San, ce sont deux découpes de chaînes imbriquées. Par exemple, s=" 1 2 3 "; echo \""${s%1 2 3 }"\"couperait tout de la fin, renvoyant le leader " ". Subbing 1 2 3 avec lui [![:space:]]*dit de "trouver le premier caractère non-espace, puis de le tapoter et tout ce qui suit". L'utilisation %%au lieu de %rend l'opération de découpage de fin gourmande. Ceci est imbriqué dans un découpage non gourmand dès le début, donc en fait, vous découpez " "depuis le début. Ensuite, échangez%, # et * pour les espaces de fin. Bam!
Mark G.

2
Je n'ai trouvé aucun effet secondaire indésirable, et le code principal fonctionne également avec d'autres shells de type POSIX. Cependant, sous Solaris 10, cela ne fonctionne pas avec /bin/sh(uniquement avec /usr/xpg4/bin/sh, mais ce n'est pas ce qui sera utilisé avec les scripts sh habituels).
vinc17

9
Beaucoup mieux que d'utiliser sed, tr etc., car c'est beaucoup plus rapide, en évitant tout fork (). Sur Cygwin, la différence de vitesse est de l'ordre de grandeur.
Gene Pavlovsky

9
@San Au début, j'étais perplexe parce que je pensais qu'il s'agissait d'expressions régulières. Ils ne sont pas. Il s'agit plutôt de la syntaxe Pattern Matching ( gnu.org/software/bash/manual/html_node/Pattern-Matching.html , wiki.bash-hackers.org/syntax/pattern ) utilisée dans la suppression des sous-chaînes ( tldp.org/LDP/abs /html/string-manipulation.html ). Ainsi ${var%%[![:space:]]*}dit "supprimer de varsa plus longue sous-chaîne qui commence par un caractère non-espace". Cela signifie que vous ne disposez que des espaces de tête, que vous supprimez par la suite ${var#... La ligne suivante (arrière) est l'inverse.
Ohad Schneider

8
C'est massivement la solution idéale. Forker un ou plusieurs processus externes (par exemple, awk, sed, tr, xargs) simplement d'une suppression des espaces chaîne unique est fondamentalement fou - en particulier lorsque la plupart des shells (y compris bash) déjà fournir la chaîne native munging installations hors-the-box.
Cecil Curry

81

Bash a une fonctionnalité appelée expansion de paramètres , qui, entre autres, permet le remplacement de chaînes basé sur des soi-disant modèles (les modèles ressemblent à des expressions régulières, mais il existe des différences et des limitations fondamentales). [ligne originale de flussence: Bash a des expressions régulières, mais elles sont bien cachées:]

Ce qui suit montre comment supprimer tout l'espace blanc (même de l'intérieur) d'une valeur variable.

$ 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
Ou plutôt, cela fonctionne pour les espaces au milieu d'un var, mais pas quand j'essaye de l'ancrer à la fin.
Paul Tomblin

Est-ce que cela aide? À partir de la page de manuel: "$ {paramètre / motif / chaîne} [...] Si le motif commence par%, il doit correspondre à la fin de la valeur étendue du paramètre."

@Ant, donc ce ne sont pas vraiment des expressions régulières, mais quelque chose de similaire?
Paul Tomblin

3
Ce sont des regex, juste un étrange dialecte.

13
${var/ /}supprime le premier caractère d'espace. ${var// /}supprime tous les caractères d'espace. Il n'y a aucun moyen de couper simplement les espaces de début et de fin avec uniquement cette construction.
Gilles 'SO- arrête d'être méchant'

60

Afin de supprimer tous les espaces du début et de la fin d'une chaîne (y compris les caractères de fin de ligne):

echo $variable | xargs echo -n

Cela supprimera également les espaces en double:

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

Produit: 'cette chaîne a beaucoup d'espaces'


5
Fondamentalement, le xargs supprime tous les délimiteurs de la chaîne. Par défaut, il utilise l'espace comme délimiteur (cela peut être modifié par l'option -d).
rkachach

4
C'est de loin la solution la plus propre (à la fois courte et lisible).
Potherca

Pourquoi en avez-vous besoin echo -n? echo " my string " | xargsa la même sortie.
bfontaine

echo -n supprime également la fin de ligne
rkachach

55

Supprimer un espace de tête et un espace de fin

trim()
{
    local trimmed="$1"

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

    echo "$trimmed"
}

Par exemple:

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

Production:

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

Supprimez tous les espaces avant et arrière

trim()
{
    local trimmed="$1"

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

    echo "$trimmed"
}

Par exemple:

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

Production:

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

9
Cela ne supprimera qu'un seul caractère d'espace. Donc, l'écho se traduit par'hello world ', 'foo bar', 'both sides '
Joe

@Joe J'ai ajouté une meilleure option.
wjandrea

42

De la section Bash Guide sur le globbing

Pour utiliser un extglob dans une extension de paramètre

 #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  

Voici la même fonctionnalité enveloppée dans une fonction (REMARQUE: besoin de citer la chaîne d'entrée passée à la fonction):

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

Usage:

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

Si nous modifions la fonction à exécuter dans un sous-shell, nous n'avons pas à nous soucier d'examiner l'option de shell actuelle pour extglob, nous pouvons simplement la définir sans affecter le shell actuel. Cela simplifie énormément la fonction. Je mets aussi à jour les paramètres de position "en place" donc je n'ai même pas besoin d'une variable locale

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

donc:

$ 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
comme vous l'avez observé, trim () supprime uniquement les espaces de début et de fin.
GuruM

Comme mkelement l'a déjà noté, vous devez passer le paramètre de fonction sous forme de chaîne entre guillemets, c'est-à-dire $ (trim "$ string") au lieu de $ (trim $ string). J'ai mis à jour le code pour montrer l'utilisation correcte. Merci.
GuruM

Autant que j'apprécie connaître les options shell, je ne pense pas que le résultat final est plus élégant que de simplement faire 2 substitutions de motif
sehe

Notez que (avec une version assez récente de Bash?), Vous pouvez simplifier le mécanisme de restauration de l'option extglob, en utilisant shopt -p: écrivez simplement local restore="$(shopt -p extglob)" ; shopt -s extglobau début de votre fonction, et eval "$restore"à la fin (sauf, accordé, eval est mauvais…).
Maëlan

Excellente solution! Une amélioration potentielle: on dirait qu'il [[:space:]]pourrait être remplacé par, eh bien, un espace: ${var##+( )}et ${var%%+( )}fonctionner aussi bien et ils sont plus faciles à lire.
DKroot

40

Vous pouvez couper simplement avec echo:

foo=" qsdqsd qsdqs q qs   "

# Not trimmed
echo \'$foo\'

# Trim
foo=`echo $foo`

# Trimmed
echo \'$foo\'

Cela réduit plusieurs espaces contigus en un seul.
Evgeni Sergeev

7
L'avez-vous essayé lorsqu'il foocontient un caractère générique? par exemple, foo=" I * have a wild card"... surprise! De plus, cela réduit plusieurs espaces contigus en un seul.
gniourf_gniourf

5
C'est une excellente solution si vous: 1. ne voulez pas d'espaces aux extrémités 2. ne voulez qu'un seul espace entre chaque mot 3. travaillez avec une entrée contrôlée sans caractères génériques. Il transforme essentiellement une liste mal formatée en une bonne.
musicin3d

Bon rappel des caractères génériques @gniourf_gniourf +1. Encore une excellente solution, Vamp. +1 à vous aussi.
Dr Beco

25

Je l'ai toujours fait avec sed

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

S'il existe une solution plus élégante, j'espère que quelqu'un l'affichera.


pourriez-vous expliquer la syntaxe sed?
farid99

2
L'expression régulière correspond à tous les espaces de fin et la remplace par rien.
Paul Tomblin

4
Que diriez-vous de diriger les espaces blancs?
Qian Chen

Cela supprime tous les espaces de fin sed -e 's/\s*$//'. Explication: «s» signifie recherche, «\ s» signifie tous les espaces, «*» signifie zéro ou plusieurs, «$» signifie jusqu'à la fin de la ligne et «//» signifie remplacer toutes les correspondances par une chaîne vide .
Craig

Dans 's / * $ //', pourquoi y a-t-il 2 espaces avant l'astérisque, au lieu d'un seul espace? Est-ce une faute de frappe?
Brent212

24

Vous pouvez supprimer les sauts de ligne avec tr:

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

8
Je ne veux pas supprimer «\ n» du milieu de la chaîne, uniquement depuis le début ou la fin.
trop de php

24

Avec les fonctionnalités étendues de correspondance de motifs de Bash activées ( shopt -s extglob), vous pouvez utiliser ceci:

{trimmed##*( )}

pour supprimer une quantité arbitraire d'espaces de tête.


Terrifiant! Je pense que c'est la solution la plus légère et la plus élégante.
dubiousjim

1
Voir le post de @ GuruM ci-dessous pour une solution similaire, mais plus générique qui (a) traite de toutes les formes d'espaces blancs et (b) gère également les espaces blancs à la fin .
mklement0

@mkelement +1 pour avoir pris la peine de réécrire mon extrait de code en tant que fonction. Merci
GuruM

Fonctionne également avec / bin / ksh par défaut d'OpenBSD. /bin/sh -o posixfonctionne aussi mais je suis méfiant.
Clint Pachl

Pas un assistant bash ici; quoi trimmed? Est-ce une chose intégrée ou la variable qui est coupée?
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
Incroyable! Simple et efficace! Clairement ma solution préférée. Je vous remercie!
xebeche

1
@CraigMcQueen c'est la valeur de la variable, car readstockera à la variable par son nom $ 1 une version tronquée de sa valeur $ {! 1}
Aquarius Power

2
Le paramètre de la fonction trim () est un nom de variable: voir l'appel à trim () dans test_trim (). Dans trim () comme appelé depuis test_trim (), $ 1 se développe en foo et $ {! 1} se développe en $ foo (c'est-à-dire au contenu actuel de la variable foo). Recherchez dans le manuel bash «indirection variable».
flabdablet

1
Que diriez-vous de cette petite modification, pour prendre en charge le découpage de plusieurs vars en un seul appel? trim() { while [[ $# -gt 0 ]]; do read -rd '' $1 <<<"${!1}"; shift; done; }
Gene Pavlovsky

2
@AquariusPower, il n'est pas nécessaire d'utiliser l'écho dans un sous-shell pour la version à une ligne, read -rd '' str <<<"$str"fera l'affaire.
flabdablet

12

Il y a beaucoup de réponses, mais je crois toujours que mon script qui vient d'être écrit mérite d'être mentionné parce que:

  • il a été testé avec succès dans le shell bash / dash / busybox
  • il est extrêmement petit
  • il ne dépend pas de commandes externes et n'a pas besoin de bifurquer (-> utilisation rapide et faible des ressources)
  • cela fonctionne comme prévu:
    • il dépouille tout espaces et les tabulations du début et de la fin, mais pas plus
    • important: il ne supprime rien du milieu de la chaîne (de nombreuses autres réponses le font), même les nouvelles lignes resteront
    • spécial: le "$*"joint plusieurs arguments en utilisant un seul espace. si vous voulez couper et sortir uniquement le premier argument, utilisez"$1" plutôt
    • si n'a aucun problème avec les modèles de noms de fichiers correspondants, etc.

Le scénario:

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

Usage:

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

Production:

>here     is
    something<

Bah in C ce serait beaucoup plus simple à mettre en œuvre!
Nils

Sûr. Malheureusement, ce n'est pas C et parfois vous voulez éviter d'appeler des outils externes
Daniel Alder

Pour rendre le code à la fois plus lisible et compatible avec le copier-coller, vous pouvez changer les crochets en caractères d' [\ \t]
échappement

@leondepeon avez-vous essayé cela? J'ai essayé quand je l'ai écrit et réessayé, et votre suggestion ne fonctionne pas dans bash, dash, busybox
Daniel Alder

@DanielAlder Je l'ai fait, mais comme il y a déjà 3 ans, je ne trouve pas le code où je l'ai utilisé. Maintenant, cependant, j'utiliserais probablement [[:space:]]comme dans l'une des autres réponses: stackoverflow.com/a/3352015/3968618
leondepeon

11

Vous pouvez utiliser la vieille école tr. Par exemple, cela renvoie le nombre de fichiers modifiés dans un référentiel git, espaces blancs supprimés.

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

1
Cela ne coupe pas les espaces avant et arrière - il supprime tous les espaces de la chaîne.
Nick

11

Cela a fonctionné pour moi:

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>

Pour mettre cela sur moins de lignes pour le même résultat:

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

1
Ça n'a pas marché pour moi. Le premier a imprimé une chaîne non coupée. Le second a lancé une mauvaise substitution. Pouvez-vous expliquer ce qui se passe ici?
musicin3d

1
@ musicin3d: c'est un site que j'utilise fréquemment et qui explique comment fonctionne la manipulation des variables dans bash search ${var##Pattern}pour plus de détails. En outre, ce site explique les modèles de bash . Ainsi, les ##moyens suppriment le motif donné de l'avant et les %%moyens suppriment le motif donné de l'arrière. La +( )partie est le motif et cela signifie "une ou plusieurs occurrences d'un espace"
gMale

Drôle, cela a fonctionné dans l'invite, mais pas après la transposition dans le fichier de script bash.
Dr Beco

bizarre. Est-ce la même version bash dans les deux cas?
gMale

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

OU

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

OU

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

OU

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

OU

S'appuyer sur l'expr soulution de moskit ...

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

OU

# 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

J'ai vu des scripts utiliser simplement l'affectation de variables pour faire le travail:

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

Les espaces sont automatiquement fusionnés et rognés. Il faut faire attention aux métacaractères du shell (risque d'injection potentiel).

Je recommanderais également toujours de remplacer les variables par des guillemets doubles dans les conditions de la coque:

if [ -n "$var" ]; then

car quelque chose comme un -o ou un autre contenu dans la variable pourrait modifier vos arguments de test.


3
C'est l'utilisation non cotée de $xyzavec echoqui fait la fusion des espaces blancs, pas l'affectation des variables. Pour stocker la valeur ajustée dans la variable de votre exemple, vous devez utiliser xyz=$(echo -n $xyz). De plus, cette approche est sujette à une expansion potentiellement indésirable des chemins (globbing).
mklement0

c'est juste faux, la valeur de la xyzvariable n'est PAS coupée.
caesarsol

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

1
Cela ne fonctionnera pas s'il y a plus d'un espace entre deux mots. Essayez: echo $(echo "1 2 3")(avec deux espaces entre 1, 2 et 3).
joshlf

7

J'utiliserais simplement sed:

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

a) Exemple d'utilisation sur une chaîne à ligne unique

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

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

Production:

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

b) Exemple d'utilisation sur une chaîne multiligne

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

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

Production:

GIVEN STRING: |    wordAA
   >wordB<
wordC    |

TRIMMED STRING: |wordAA
   >wordB<
wordC|

c) Remarque finale:
Si vous n'aimez pas utiliser une fonction, pour une chaîne à une seule ligne, vous pouvez simplement utiliser une commande "plus facile à retenir" comme:

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

Exemple:

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

Production:

wordA wordB wordC

L'utilisation de ce qui précède sur les chaînes multi-lignes fonctionnera également , mais veuillez noter que cela réduira également tout espace multiple interne à la fin / en tête, comme GuruM l'a remarqué dans les commentaires.

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

Production:

wordAA
>four spaces before<
>one space before<

Donc, si cela vous dérange de garder ces espaces, veuillez utiliser la fonction au début de ma réponse!

d) EXPLICATION de la syntaxe sed "find and replace" sur les chaînes multilignes utilisées à l'intérieur de la fonction trim:

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

Remarque: Comme suggéré par @mkelement, cela ne fonctionnera pas pour les chaînes multi-lignes bien qu'il devrait fonctionner pour les chaînes mono-lignes.
GuruM

1
Vous vous trompez: cela fonctionne aussi sur les chaînes multi-lignes. Testez-le! :)
Luca Borrione

+1 pour l'utilisation - m'a permis de tester facilement le code. Cependant, le code ne fonctionnera toujours pas pour les chaînes multi-lignes. Si vous regardez attentivement la sortie, vous remarquerez que tous les espaces internes de début / fin sont également supprimés, par exemple l'espace devant "multi-ligne" est remplacé par "multi-ligne". Essayez simplement d'augmenter le nombre d'espaces de début / fin sur chaque ligne.
GuruM

Maintenant je vois ce que tu veux dire! Merci pour la tête haute, j'ai édité ma réponse.
Luca Borrione

@ "Luca Borrione" - bienvenue :-) Pourriez-vous expliquer la syntaxe sed que vous utilisez dans trim ()? Cela pourrait également aider n'importe quel utilisateur de votre code à l'ajuster à d'autres utilisations. Cela pourrait même aider à trouver des cas limites pour l'expression régulière.
GuruM

6

Voici une fonction trim () qui coupe et normalise les espaces blancs

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

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

Et une autre variante qui utilise des expressions régulières.

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

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

La première approche est délicate en ce qu'elle non seulement normalise les espaces intérieurs (remplace toutes les étendues intérieures des espaces par un seul espace chacun), mais est également sujette à la globalisation (expansion du nom de chemin) de sorte que, par exemple, un *caractère dans la chaîne d'entrée soit étendre à tous les fichiers et dossiers du dossier de travail actuel. Enfin, si $ IFS est défini sur une valeur non définie par défaut, le découpage peut ne pas fonctionner (bien que cela soit facilement résolu par l'ajout local IFS=$' \t\n'). Le découpage est limité aux formes d'espaces suivants: les espaces \tet les \ncaractères.
mklement0

1
La seconde approche basée sur les expressions régulières est excellente et sans effets secondaires, mais dans sa forme actuelle est problématique: (a) sur bash v3.2 +, la correspondance ne fonctionnera pas par défaut, car l'expression régulière doit être non citée dans l'ordre pour fonctionner et (b) l'expression régulière elle-même ne gère pas le cas où la chaîne d'entrée est un seul caractère non espace entouré d'espaces. Pour résoudre ces problèmes, remplacez la ifligne avec: if [[ "$trimmed" =~ ' '*([^ ]|[^ ].*[^ ])' '* ]]. Enfin, l'approche ne concerne que les espaces, pas les autres formes d'espaces (voir mon prochain commentaire).
mklement0

2
La fonction qui utilise des expressions régulières ne traite que les espaces et non les autres formes d'espaces, mais il est facile de généraliser: Remplacez la ifligne par:[[ "$trimmed" =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]
mklement0

6

Utilisez AWK:

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

Doux qui semble fonctionner (ex :) $stripped_version=echo $ var | awk '{gsub (/ ^ + | + $ /, "")} 1' '
rogerdpack

4
sauf que awk ne fait rien: l'écho d'une variable non cotée a déjà supprimé les espaces
glenn jackman

6

Les affectations ignorent les espaces blancs de début et de fin et, en tant que telles, peuvent être utilisées pour couper:

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

8
Ce n'est pas vrai. C'est "l'écho" qui supprime les espaces, pas l'affectation. Dans votre exemple, faites echo "$var"pour voir la valeur avec des espaces.
Nicholas Sushkin

2
@NicholasSushkin On pourrait le faire var=$(echo $var)mais je ne le recommande pas. Les autres solutions présentées ici sont préférées.
xebeche

5

Cela ne pose pas de problème avec les globes indésirables, également, l'espace blanc intérieur n'est pas modifié (en supposant que $IFS est défini par défaut, ce qui est ' \t\n').

Il lit jusqu'à la première nouvelle ligne (et ne l'inclut pas) ou la fin de la chaîne, selon la première éventualité, et supprime tout mélange d'espaces et de \tcaractères de début et de fin . Si vous souhaitez conserver plusieurs lignes (et également supprimer les sauts de ligne de début et de fin), utilisez read -r -d '' var << eofplutôt; notez cependant que si votre entrée contient \neof, elle sera coupée juste avant. ( D' autres formes d'espace blanc, à savoir \r, \fet \v, sont ne pas supprimées, même si vous les ajoutez à $ IFS.)

read -r var << eof
$var
eof


5

Cela supprimera tous les espaces blancs de votre chaîne,

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

/remplace la première occurrence et //toutes les occurrences d'espaces dans la chaîne. C'est-à-dire que tous les espaces blancs sont remplacés par - rien


4

C'est la méthode la plus simple que j'ai vue. Il utilise uniquement Bash, ce ne sont que quelques lignes, l'expression rationnelle est simple et correspond à toutes les formes d'espaces:

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

Voici un exemple de script pour le tester:

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
Sûrement préférable à, par exemple (vous dieux!), Bombarder Python. Sauf que je pense qu'il est plus simple et plus général de gérer correctement une chaîne qui ne contient que des espaces. L'expression légèrement simplifiée serait:^[[:space:]]*(.*[^[:space:]])?[[:space:]]*$
Ron Burk

4

Python a une fonction strip() qui fonctionne de manière identique à PHP trim(), donc nous pouvons simplement faire un peu de Python en ligne pour en faire un utilitaire facilement compréhensible:

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

Cela supprimera les espaces de début et de fin (y compris les retours à la ligne).

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

pendant que cela fonctionne, vous voudrez peut-être envisager d'offrir une solution qui n'implique pas le lancement d'un interpréteur python complet juste pour couper une chaîne. C'est juste du gaspillage.
pdwalker du

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

J'ai trouvé que j'avais besoin d'ajouter du code à partir d'une sdiffsortie en désordre pour le nettoyer:

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'

Cela supprime les espaces de fin et autres caractères invisibles.


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.