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?

17
demandé sur Flimzy 2011-05-07 01:59:10

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

10
répondu Daniel Martin 2011-05-06 22:39:40

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.

6
répondu jilles 2011-05-06 22:25:00

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.

6
répondu Alanyst 2011-05-06 22:26:20

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
1
répondu bobpaul 2016-03-06 18:14:09

${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'

0
répondu user3304852 2014-02-13 07:08:29

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
0
répondu Larry 2017-06-16 19:16:59