Bash Bad substitution avec subshell et substring
Un exemple artificiel... étant donné
FOO="/foo/bar/baz"
cela fonctionne (en bash)
BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1} # result is BAZ="b"
ce n'est pas
BAZ=${$(basename $FOO):0:1} # result is bad substitution
ma question Est de savoir quelle règle fait que cette [substitution de subshell] n'est pas correctement évaluée? Et quelle est la bonne façon, le cas échéant, de le faire en 1 hop?
6 réponses
tout d'Abord, notez que quand vous dites ceci:
BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1} # result is BAZ="b"
le premier bit dans la construction BAZ
BAR
et pas valeur que vous voulez prendre le premier caractère de. Donc, même si bash permettait aux noms de variables de contenir des caractères arbitraires, votre résultat dans la seconde expression ne serait pas ce que vous voulez.
Cependant, pour ce qui est de la règle qui empêche cela, permettez-moi de citer la page de bash man:
DEFINITIONS
The following definitions are used throughout the rest of this docu‐
ment.
blank A space or tab.
word A sequence of characters considered as a single unit by the
shell. Also known as a token.
name A word consisting only of alphanumeric characters and under‐
scores, and beginning with an alphabetic character or an under‐
score. Also referred to as an identifier.
Puis un peu plus tard:
PARAMETERS
A parameter is an entity that stores values. It can be a name, a num‐
ber, or one of the special characters listed below under Special Param‐
eters. A variable is a parameter denoted by a name. A variable has a
value and zero or more attributes. Attributes are assigned using the
declare builtin command (see declare below in SHELL BUILTIN COMMANDS).
Et plus tard, quand il définit la syntaxe vous vous posez sur:
${parameter:offset:length}
Substring Expansion. Expands to up to length characters of
parameter starting at the character specified by offset.
ainsi les règles telles qu'articulées dans la page de manuel disent que le ${foo:x:y}
construire doit avoir un paramètre comme la première partie, et qu'un paramètre ne peut être un nom, un numéro, ou l'un des rares paramètre spécial caractères. $(basename $FOO)
n'est pas une des possibilités permises pour un paramètre.
Comme un moyen de faire cela en une seule affectation, utilisez un tuyau d'autres commandes mentionné dans d'autres réponses.
formes modifiées de substitution de paramètre telles que ${parameter#word}
ne peut modifier qu'un paramètre, pas un mot arbitraire.
Dans ce cas, vous pouvez rediriger la sortie de basename
pour la commande dd, comme
BAR=$(basename -- "$FOO" | dd bs=1 count=1 2>/dev/null)
(Si vous voulez plus ce nombre est élevé, augmentation count
et non bs
, sinon vous pouvez obtenir moins d'octets que demandé.)
dans le cas général, il n'y a aucun moyen de faire des choses comme ça en une seule affectation.
Il échoue parce que ${BAR:0:1}
est une extension variable. Bash s'attend à voir un nom de variable après ${
, n'est pas une valeur.
Je ne suis pas au courant d'une façon de le faire dans une seule expression.
Comme d'autres l'ont dit, le premier paramètre de ${} doit être un nom de variable. Mais vous pouvez utiliser un autre subshell pour approximer ce que vous essayez de faire.
au Lieu de:
BAZ=${$(basename $FOO):0:1} # result is bad substitution
Utilisation:
BAZ=$(_TMP=$(basename $FOO);${_TMP:0:1}) # this works
${string:0:1},la chaîne doit être un nom de variable
par exemple:
FOO= "/ foo / bar/baz"
baz="toto"
BAZ=eval echo '${'"$(basename $FOO)"':0:1}'
echo $BAZ
le résultat est 'f'
une solution inventée pour votre exemple invraisemblable:
BAZ=$(expr $(basename $FOO) : '\(.\)')
comme suit:
$ FOO=/abc/def/ghi/jkl
$ BAZ=$(expr $(basename $FOO) : '\(.\)')
$ echo $BAZ
j