Bash bad substitution con subshell y subcadena
string (6)
Un ejemplo artificial ... dado
FOO="/foo/bar/baz"
esto funciona (en bash)
BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1} # result is BAZ="b"
esto no
BAZ=${$(basename $FOO):0:1} # result is bad substitution
Mi pregunta es ¿qué regla hace que esta [sustitución de subshell] se evalúe incorrectamente? ¿Y cuál es la forma correcta, si la hay, de hacer esto en 1 salto?
$ {string: 0: 1}, string debe ser un nombre de variable
por ejemplo:
FOO = "/ foo / bar / baz"
baz = "foo"
BAZ = eval echo ''${''"$(basename $FOO)"'':0:1}''
echo $ BAZ
el resultado es ''f''
Como han dicho otros, el primer parámetro de $ {} debe ser un nombre de variable. Pero puedes usar otra subshell para aproximarte a lo que estás tratando de hacer.
En lugar de:
BAZ=${$(basename $FOO):0:1} # result is bad substitution
Utilizar:
BAZ=$(_TMP=$(basename $FOO);${_TMP:0:1}) # this works
En primer lugar, tenga en cuenta que cuando dice esto:
BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1} # result is BAZ="b"
El primer bit en la construcción para BAZ
es BAR
y no el valor del que desea tomar el primer carácter. Entonces, incluso si bash permitiera que los nombres de las variables contengan caracteres arbitrarios, su resultado en la segunda expresión no sería lo que desea.
Sin embargo, en cuanto a la regla que lo impide, permítame citar desde la página de manual de bash:
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.
Luego un poco más tarde:
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).
Y luego, cuando define la sintaxis sobre la que estás preguntando:
${parameter:offset:length}
Substring Expansion. Expands to up to length characters of
parameter starting at the character specified by offset.
Por lo tanto, las reglas como se articulan en la página del manual dicen que la construcción ${foo:x:y}
debe tener un parámetro como la primera parte, y que un parámetro solo puede ser un nombre, un número o uno de los pocos caracteres de parámetros especiales. . $(basename $FOO)
no es una de las posibilidades permitidas para un parámetro.
En cuanto a la forma de hacer esto en una asignación, use una canalización a otros comandos como se menciona en otras respuestas.
Falla porque ${BAR:0:1}
es una expansión variable. Bash espera ver un nombre de variable después de ${
, no un valor.
No soy consciente de una manera de hacerlo en una sola expresión.
Las formas modificadas de sustitución de parámetros como ${parameter#word}
solo pueden modificar un parámetro, no una palabra arbitraria.
En este caso, puede canalizar la salida de basename
a un comando dd, como
BAR=$(basename -- "$FOO" | dd bs=1 count=1 2>/dev/null)
(Si desea un conteo más alto, aumente el count
y no bs
, de lo contrario puede obtener menos bytes de los solicitados).
En el caso general, no hay manera de hacer cosas como esta en una tarea.
Una solución artificial para su ejemplo artificial:
BAZ=$(expr $(basename $FOO) : ''/(./)'')
como en
$ FOO=/abc/def/ghi/jkl
$ BAZ=$(expr $(basename $FOO) : ''/(./)'')
$ echo $BAZ
j