J'écris un script qui doit calculer le nombre de caractères dans la sortie d'une commande en une seule étape .
Par exemple, l'utilisation de la commande readlink -f /etc/fstab
doit renvoyer 10
car la sortie de cette commande comporte 10 caractères.
C'est déjà possible avec des variables stockées à l'aide du code suivant:
variable="somestring";
echo ${#variable};
# 10
Malheureusement, l'utilisation de la même formule avec une chaîne générée par commande ne fonctionne pas:
${#(readlink -f /etc/fstab)};
# bash: ${#(readlink -f /etc/fstab)}: bad substitution
Je comprends qu'il est possible de le faire en enregistrant d'abord la sortie dans une variable:
variable=$(readlink -f /etc/fstab);
echo ${#variable};
Mais je voudrais supprimer l'étape supplémentaire.
Est-ce possible? La compatibilité avec le shell Almquist (sh) en utilisant uniquement des utilitaires intégrés ou standard est préférable.
readlink -f /etc/fstab
est de 11 caractères. N'oubliez pas la nouvelle ligne. Sinon, vous verriez/etc/fstabluser@cern:~$
quand vous l'avez exécuté à partir d'un shell.Réponses:
Avec GNU expr :
L'
+
il est une caractéristique particulière de GNUexpr
pour vous assurer que le prochain argument est traité comme une chaîne , même si elle se trouve être unexpr
opérateur commematch
,length
,+
...Ce qui précède supprimera toute nouvelle ligne de sortie. Pour contourner ce problème:
Le résultat a été soustrait à 2, car la dernière ligne de
readlink
et le caractère que.
nous avons ajouté.Avec la chaîne Unicode,
expr
ne semble pas fonctionner, car elle renvoie la longueur de la chaîne en octets au lieu du nombre de caractères (voir ligne 654 )Vous pouvez donc utiliser:
POSIX:
L'espace avant la substitution de commande empêche la commande de planter avec la chaîne commençant par
-
, nous devons donc soustraire 3.la source
LC_ALL=C.UTF-8
, ce qui simplifie considérablement les choses si l'encodage de la chaîne n'est pas connu à l'avance.expr length $(echo "*")
- Nan. Au moins utiliser des guillemets doubles:expr length "$(…)"
. Mais cela supprime les nouvelles lignes de la commande, c'est une caractéristique incontournable de la substitution de commandes. (Vous pouvez contourner cela, mais la réponse devient alors encore plus complexe.)Je ne sais pas comment faire cela avec les commandes internes de shell (cependant Gnouc ), mais les outils standard peuvent aider:
Vous pouvez utiliser
wc -m
qui compte les caractères. Malheureusement, il compte également la nouvelle ligne finale, vous devez donc vous en débarrasser en premier:Vous pouvez bien sûr utiliser
awk
Ou Perl
la source
expr
est un intégré? Dans quelle coquille?Je le fais habituellement comme ceci:
Pour faire des commandes, je l'adapterais ainsi:
Cette approche est similaire à ce que vous faisiez dans vos 2 étapes, sauf que nous les combinons en une seule doublure.
la source
-m
place de-c
. Avec les caractères Unicode, votre approche sera rompue.readlink -f /etc/fstab | wc -m
?${#variable}
? Utilisez au moins des guillemets doublesecho -n "$variable"
, mais cela échoue toujours si, par exemple, la valeur devariable
est-e
. Lorsque vous l'utilisez en combinaison avec une substitution de commande, gardez à l'esprit que les retours à la ligne de fin sont supprimés.Vous pouvez appeler des utilitaires externes (voir les autres réponses), mais ils ralentiront votre script et il est difficile de bien faire la plomberie.
Zsh
Dans zsh, vous pouvez écrire
${#$(readlink -f /etc/fstab)}
pour obtenir la longueur de la substitution de commande. Notez que ce n'est pas la longueur de la sortie de la commande, c'est la longueur de la sortie sans retour à la ligne.Si vous voulez la longueur exacte de la sortie, sortez un caractère supplémentaire non-retour à la fin et soustrayez-en un.
Si ce que vous voulez est la charge utile dans la sortie de la commande, vous devez soustraire deux ici, car la sortie de
readlink -f
est le chemin canonique plus une nouvelle ligne.Cela diffère du
${#$(readlink -f /etc/fstab)}
cas rare mais possible où le chemin canonique lui-même se termine par une nouvelle ligne.Pour cet exemple spécifique, vous n'avez absolument pas besoin d'un utilitaire externe, car zsh a une construction intégrée équivalente à
readlink -f
, via le modificateur d'historiqueA
.Pour obtenir la longueur, utilisez le modificateur d'historique dans une extension de paramètre:
Si vous avez le nom de fichier dans une variable
filename
, ce serait${#filename:A}
.Coquilles de style Bourne / POSIX
Aucun des shells Bourne / POSIX purs (Bourne, ash, mksh, ksh93, bash, yash…) n'a une extension similaire à ma connaissance. Si vous devez appliquer une substitution de paramètres à la sortie d'une substitution de commandes ou imbriquer des substitutions de paramètres, utilisez des étapes successives.
Vous pouvez bourrer le traitement dans une fonction si vous le souhaitez.
ou
mais il n'y a généralement aucun avantage; sauf avec ksh93, cela permet à un fork supplémentaire de pouvoir utiliser la sortie de la fonction, donc cela rend votre script plus lent, et il y a rarement un avantage de lisibilité.
Encore une fois, la sortie de
readlink -f
est le chemin canonique plus une nouvelle ligne; si vous voulez la longueur du chemin canonique, soustrayez 2 au lieu de 1 pocommand_output_length
. L'utilisationcommand_output_length_sans_trailing_newlines
donne le bon résultat uniquement lorsque le chemin canonique lui-même ne se termine pas par une nouvelle ligne.Octets vs caractères
${#…}
est censé être la longueur en caractères, pas en octets, ce qui fait la différence dans les paramètres régionaux multi-octets. Des versions raisonnablement à jour de ksh93, bash et zsh calculent la longueur en caractères selon la valeur deLC_CTYPE
au moment où la${#…}
construction est développée. De nombreux autres shells courants ne prennent pas vraiment en charge les paramètres régionaux multioctets: à partir du tiret 0.5.7, mksh 46 et posh 0.12.3,${#…}
renvoie la longueur en octets. Si vous voulez que la longueur en caractères soit fiable, utilisez l'wc
utilitaire:Tant que
$LC_CTYPE
désigne un paramètre régional valide, vous pouvez être sûr que cela entraînera une erreur (sur une plate-forme ancienne ou restreinte qui ne prend pas en charge les paramètres régionaux à plusieurs octets) ou renverra la longueur correcte en caractères. (Pour Unicode, «longueur en caractères» signifie le nombre de points de code - le nombre de glyphes est encore une autre histoire, en raison de complications telles que la combinaison de caractères.)Si vous voulez la longueur en octets, définissez
LC_CTYPE=C
temporairement ou utilisez à lawc -c
place dewc -m
.Le comptage d'octets ou de caractères avec
wc
inclut tous les retours à la ligne de fin de la commande. Si vous voulez la longueur du chemin canonique en octets, c'estPour l'obtenir en caractères, soustrayez 2.
la source
echo .
ajoute deux caractères, mais le deuxième caractère est une nouvelle ligne de fin qui est supprimée par la substitution de commande.readlink
sortie, plus le.
byecho
. Nous sommes tous deux d'accord pourecho .
ajouter deux caractères, mais la nouvelle ligne de fin a été supprimée. Essayez avecprintf .
ou voyez ma réponse unix.stackexchange.com/a/160499/38906 .readlink
est la cible du lien plus une nouvelle ligne.Cela fonctionne,
dash
mais cela nécessite que la variable cible soit définitivement vide ou non définie. C'est pourquoi il s'agit en fait de deux commandes - je vide explicitement$l
dans la première:PRODUCTION
C'est tout ce qui est intégré au shell - sans inclure le
readlink
cours bien sûr - mais l'évaluer de cette façon dans le shell actuel implique que vous devez effectuer l'affectation avant d'obtenir le len, c'est pourquoi%.s
j'ilence le premier argument dans laprintf
chaîne de format et l'ajoute à nouveau pour la valeur littérale à la fin deprintf
la liste arg.Avec
eval
:PRODUCTION
Vous pouvez vous rapprocher de la même chose, mais au lieu de la sortie dans une variable dans la première commande, vous l'obtenez sur stdout:
... qui écrit ...
... au fichier descripteur 1 sans affecter de valeur à aucun vars dans le shell courant.
la source
variable=$(readlink -f /etc/fstab); echo ${#variable};
mais je voudrais supprimer l'étape supplémentaire."expr
, par exemple. Cela n'a probablement d' importance que si, d'une manière ou d'une autre, le len obstrue la valeur, j'avoue que j'ai du mal à comprendre pourquoi cela peut être, mais je soupçonne qu'il pourrait y avoir un cas dans lequel cela importerait.eval
fait, le chemin est probablement le plus propre ici - il affecte la sortie et le len au même nom de var en une seule exécution - très proche de fairel=length(l):out(l)
. Faire -expr length $(command)
t occlure la valeur en faveur de la len, par la voie.