Si ha estado siguiendo unix.stackexchange.com durante un tiempo, debería saber que dejar una variable sin comillas en el contexto de la lista (como en echo $var
) en los shells Bourne / POSIX (zsh es la excepción) tiene un significado muy especial y no debe hacerse a menos que tenga una muy buena razón para hacerlo.
Se analiza en profundidad en una serie de preguntas y respuestas aquí (ejemplos: ? ¿Por qué la cáscara estrangulador secuencia de comandos en el espacio en blanco u otros caracteres especiales , cuando es de doble citando necesario? , Expansión de una variable de shell y el efecto de pegote y se dividió en él , Citado vs expansión de cadena sin comillas)
Ese ha sido el caso desde el lanzamiento inicial del shell Bourne a finales de los 70 y no ha sido modificado por el shell Korn (uno de los mayores remordimientos de David Korn (pregunta # 7) ) o bash
que mayormente copió el shell Korn, y eso es cómo ha sido especificado por POSIX / Unix.
Ahora, todavía estamos viendo una serie de respuestas aquí e incluso ocasionalmente lanzamos código de shell público donde las variables no se citan. Hubieras pensado que la gente ya habría aprendido.
En mi experiencia, hay principalmente 3 tipos de personas que omiten citar sus variables:
principiantes Esos pueden ser excusados ya que es una sintaxis completamente intuitiva. Y es nuestro papel en este sitio educarlos.
gente olvidadiza
personas que no están convencidas incluso después de martilleos repetidos, que piensan que seguramente el autor de Bourne shell no tenía la intención de citar todas nuestras variables .
Tal vez podamos convencerlos si exponemos el riesgo asociado con este tipo de comportamientos.
¿Qué es lo peor que puede suceder si olvida citar sus variables? ¿Es realmente tan malo?
¿De qué tipo de vulnerabilidad estamos hablando aquí?
¿En qué contextos puede ser un problema?
Respuestas:
Preámbulo
Primero, diría que no es la forma correcta de abordar el problema. Es un poco como decir " no deberías asesinar personas porque de lo contrario irás a la cárcel ".
Del mismo modo, no cita su variable porque de lo contrario está introduciendo vulnerabilidades de seguridad. Usted cita sus variables porque está mal no hacerlo (pero si el temor a la cárcel puede ayudar, ¿por qué no?).
Un pequeño resumen para aquellos que acaban de subirse al tren.
En la mayoría de los shells, dejar una expansión variable sin comillas (aunque eso (y el resto de esta respuesta) también se aplica a la sustitución de comandos (
`...`
o$(...)
) y la expansión aritmética ($((...))
o$[...]
)) tiene un significado muy especial. La mejor manera de describirlo es que es como invocar algún tipo de operador implícito split + glob¹ .en otro idioma se escribiría algo como:
$var
primero se divide en una lista de palabras de acuerdo con reglas complejas que involucran el$IFS
parámetro especial (la parte dividida ) y luego cada palabra resultante de esa división se considera como un patrón que se expande a una lista de archivos que coinciden (la parte global ) .Como ejemplo, si
$var
contiene*.txt,/var/*.xml
y$IFS
contiene,
,cmd
se llamaría con varios argumentos, siendo el primerocmd
y los siguientes lostxt
archivos en el directorio actual y losxml
archivos en/var
.Si quisieras llamar
cmd
solo con los dos argumentos literalescmd
y*.txt,/var/*.xml
escribirías:que estaría en tu otro idioma más familiar:
¿Qué queremos decir con vulnerabilidad en un shell ?
Después de todo, se sabe desde los albores del tiempo que los scripts de shell no deben usarse en contextos sensibles a la seguridad. Seguramente, OK, dejar una variable sin comillas es un error, pero eso no puede hacer tanto daño, ¿verdad?
Bueno, a pesar del hecho de que alguien le diría que los scripts de shell nunca deberían usarse para CGIs web, o que afortunadamente la mayoría de los sistemas no permiten scripts de shell setuid / setgid hoy en día, una cosa que es shellshock (el error bash explotable remotamente que hizo que los titulares en septiembre de 2014) revelados es que los shells todavía se usan ampliamente donde probablemente no deberían: en CGI, en scripts de enlace de cliente DHCP, en comandos sudoers, invocados por (si no como ) comandos setuid ...
A veces sin saberlo. Por ejemplo,
system('cmd $PATH_INFO')
en un scriptphp
/perl
/python
CGI invoca un shell para interpretar esa línea de comando (sin mencionar el hecho de que encmd
sí mismo puede ser un script de shell y su autor puede nunca haber esperado que se llame desde un CGI).Tienes una vulnerabilidad cuando hay un camino para la escalada de privilegios, es decir, cuando alguien (llamémosle el atacante ) puede hacer algo que no debe hacer.
Invariablemente, eso significa que el atacante proporciona datos, que los datos están siendo procesados por un usuario / proceso privilegiado que inadvertidamente hace algo que no debería estar haciendo, en la mayoría de los casos debido a un error.
Básicamente, tienes un problema cuando tu código con errores procesa datos bajo el control del atacante .
Ahora, no siempre es obvio de dónde provienen esos datos , y a menudo es difícil saber si su código alguna vez podrá procesar datos no confiables.
En lo que respecta a las variables, en el caso de un script CGI, es bastante obvio, los datos son los parámetros CGI GET / POST y cosas como cookies, ruta, host ... parámetros.
Para un script setuid (que se ejecuta como un usuario cuando es invocado por otro), son los argumentos o las variables de entorno.
Otro vector muy común son los nombres de archivo. Si obtiene una lista de archivos de un directorio, es posible que el atacante haya plantado archivos allí .
En ese sentido, incluso cuando se le solicite un shell interactivo, podría ser vulnerable (al procesar archivos en,
/tmp
o~/tmp
por ejemplo).Incluso a
~/.bashrc
puede ser vulnerable (por ejemplo,bash
lo interpretará cuando se invoquessh
para ejecutar unForcedCommand
like engit
implementaciones de servidor con algunas variables bajo el control del cliente).Ahora, un script no puede ser llamado directamente para procesar datos no confiables, pero puede ser llamado por otro comando que lo haga. O su código incorrecto puede ser copiado en scripts que lo hagan (por usted 3 años más adelante o uno de sus colegas). Un lugar donde es particularmente crítico es en las respuestas en los sitios de preguntas y respuestas, ya que nunca sabrá dónde pueden terminar las copias de su código.
Abajo a los negocios; ¿qué tan malo es?
Dejar una variable (o sustitución de comando) sin comillas es, con mucho, la fuente número uno de vulnerabilidades de seguridad asociadas con el código de shell. En parte porque esos errores a menudo se traducen en vulnerabilidades, pero también porque es muy común ver variables sin comillas.
En realidad, al buscar vulnerabilidades en el código de shell, lo primero que debe hacer es buscar variables sin comillas. Es fácil de detectar, a menudo un buen candidato, generalmente fácil de rastrear a los datos controlados por el atacante.
Hay un número infinito de formas en que una variable sin comillas puede convertirse en una vulnerabilidad. Solo daré algunas tendencias comunes aquí.
Divulgación de información
La mayoría de las personas se toparán con errores asociados con variables no citadas debido a la parte dividida (por ejemplo, es común que los archivos tengan espacios en sus nombres hoy en día y el espacio está en el valor predeterminado de IFS). Mucha gente pasará por alto la parte glob . La parte glob es al menos tan peligrosa como la parte dividida .
La aplicación de globo realizada sobre una entrada externa no desinfectada significa que el atacante puede hacerle leer el contenido de cualquier directorio.
En:
si
$unsanitised_external_input
contiene/*
, eso significa que el atacante puede ver el contenido de/
. No es gran cosa. Sin embargo, se vuelve más interesante con lo/home/*
que le brinda una lista de nombres de usuario en la máquina/tmp/*
,/home/*/.forward
para obtener sugerencias sobre otras prácticas peligrosas,/etc/rc*/*
para servicios habilitados ... No es necesario nombrarlos individualmente. Un valor de/* /*/* /*/*/*...
solo enumerará todo el sistema de archivos.Denegación de vulnerabilidades de servicio.
Llevando el caso anterior demasiado lejos y tenemos un DoS.
En realidad, cualquier variable sin comillas en el contexto de la lista con entrada no higiénica es al menos una vulnerabilidad DoS.
Incluso los scripters de shell expertos suelen olvidarse de citar cosas como:
:
es el comando no-op. ¿Qué podría salir mal?Eso está destinado a asignar
$1
a$QUERYSTRING
if no$QUERYSTRING
estaba configurado. Esa es una manera rápida de hacer que un script CGI sea invocable desde la línea de comandos también.Sin
$QUERYSTRING
embargo, todavía se expande y, como no se cita, se invoca el operador split + glob .Ahora, hay algunos globos que son particularmente caros de expandir. El
/*/*/*/*
primero es lo suficientemente malo, ya que significa enumerar directorios de hasta 4 niveles inferiores. Además de la actividad del disco y la CPU, eso significa almacenar decenas de miles de rutas de archivos (40k aquí en una máquina virtual de servidor mínima, 10k de los cuales directorios).Ahora
/*/*/*/*/../../../../*/*/*/*
significa 40k x 10k y/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
es suficiente para poner de rodillas incluso a la máquina más poderosa.Pruébelo usted mismo (aunque esté preparado para que su máquina se bloquee o cuelgue):
Por supuesto, si el código es:
Entonces puedes llenar el disco.
Simplemente haga una búsqueda en google en shell cgi o bash cgi o ksh cgi , y encontrará algunas páginas que le muestran cómo escribir CGI en shells. Observe cómo la mitad de los que procesan los parámetros son vulnerables.
Incluso el propio David Korn es vulnerable (mira el manejo de las cookies).
hasta vulnerabilidades de ejecución de código arbitrario
La ejecución de código arbitrario es el peor tipo de vulnerabilidad, ya que si el atacante puede ejecutar cualquier comando, no hay límite en lo que puede hacer.
Esa es generalmente la parte dividida que conduce a esos. Esa división da como resultado que se pasen varios argumentos a los comandos cuando solo se espera uno. Mientras que el primero de ellos se usará en el contexto esperado, los otros estarán en un contexto diferente, por lo que potencialmente se interpretarán de manera diferente. Mejor con un ejemplo:
Aquí, la intención era asignar el contenido de la
$external_input
variable de shell a lafoo
awk
variable.Ahora:
La segunda palabra resultante de la división de
$external_input
no se asignafoo
sino que se considera comoawk
código (aquí, que ejecuta un comando arbitrario:)uname
.Esto es especialmente un problema para los comandos que se pueden ejecutar otros comandos (
awk
,env
,sed
(GNU uno),perl
,find
...) sobre todo con las variantes de GNU (que aceptan opciones después de argumentos). A veces, no sospecharías que los comandos puedan ejecutar otros comoksh
,bash
ozsh
's[
oprintf
...Si creamos un directorio llamado
x -o yes
, entonces la prueba se vuelve positiva, porque estamos evaluando una expresión condicional completamente diferente.Peor aún, si creamos un archivo llamado
x -a a[0$(uname>&2)] -gt 1
, con todas las implementaciones de ksh al menos (que incluye lash
mayoría de los Unices comerciales y algunos BSD), que se ejecutauname
porque esos shells realizan una evaluación aritmética en los operadores de comparación numéricos del[
comando.Lo mismo
bash
para un nombre de archivo comox -a -v a[0$(uname>&2)]
.Por supuesto, si no pueden obtener una ejecución arbitraria, el atacante puede conformarse con un daño menor (lo que puede ayudar a obtener una ejecución arbitraria). Cualquier comando que pueda escribir archivos o cambiar permisos, propiedad o tener algún efecto principal o secundario podría ser explotado.
Se pueden hacer todo tipo de cosas con nombres de archivos.
Y terminas haciendo
..
grabable (recursivamente con GNUchmod
).Las secuencias de comandos que realizan el procesamiento automático de archivos en áreas públicamente grabables como
/tmp
deben escribirse con mucho cuidado.Qué pasa
[ $# -gt 1 ]
Eso es algo que encuentro exasperante. Algunas personas se molestan en preguntarse si una expansión en particular puede ser problemática para decidir si pueden omitir las citas.
Es como decir Oye, parece que
$#
no puede estar sujeto al operador split + glob, pidamos al shell que lo divida + glob . O bien , vamos a escribir un código incorrecto solo porque es poco probable que se solucione el error .Ahora, ¿qué tan improbable es? OK,
$#
(o$!
,$?
o cualquier sustitución aritmética) solo puede contener dígitos (o-
para algunos) por lo que la parte glob está fuera. Sin embargo, para que la parte dividida haga algo, todo lo que necesitamos es$IFS
contener dígitos (o-
).Con algunos proyectiles,
$IFS
puede heredarse del entorno, pero si el entorno no es seguro, se acabó el juego de todos modos.Ahora si escribes una función como:
Lo que eso significa es que el comportamiento de su función depende del contexto en el que se llama. O, en otras palabras, se
$IFS
convierte en una de las entradas. Estrictamente hablando, cuando escribe la documentación API para su función, debería ser algo como:Y el código que llama a su función debe asegurarse de
$IFS
que no contenga dígitos. Todo eso porque no tenía ganas de escribir esos 2 caracteres de comillas dobles.Ahora, para que ese
[ $# -eq 2 ]
error se convierta en una vulnerabilidad, necesitaría de alguna manera que el valor de$IFS
estar bajo el control del atacante . Posiblemente, eso normalmente no sucedería a menos que el atacante lograra explotar otro error.Sin embargo, eso no es desconocido. Un caso común es cuando las personas olvidan desinfectar los datos antes de usarlos en expresiones aritméticas. Ya hemos visto anteriormente que puede permitir la ejecución de código arbitrario en algunos shells, pero en todos ellos, permite al atacante dar a cualquier variable un valor entero.
Por ejemplo:
Y con un
$1
valor con(IFS=-1234567890)
, esa evaluación aritmética tiene el efecto secundario de la configuración IFS y el siguiente[
comando falla, lo que significa que se omite la comprobación de demasiados argumentos .¿Qué pasa cuando no se invoca el operador split + glob ?
Hay otro caso en el que se necesitan comillas alrededor de variables y otras expansiones: cuando se usa como patrón.
no probar si
$a
y$b
son los mismos (excepto conzsh
) pero si$a
coincide con el patrón en$b
. Y hay que citar$b
si usted desea comparar como cadenas (lo mismo en"${a#$b}"
o"${a%$b}"
o"${a##*$b*}"
donde$b
debe ser citado si no es para ser tomado como un patrón).Lo que esto significa es que
[[ $a = $b ]]
puede devolver cierto en los casos en que$a
es diferente de$b
(por ejemplo, cuando$a
esanything
y$b
es*
) o puede devolver false cuando son idénticos (por ejemplo, cuando ambos$a
y$b
son[a]
).¿Puede eso crear una vulnerabilidad de seguridad? Sí, como cualquier error. Aquí, el atacante puede alterar el flujo de código lógico de su script y / o romper las suposiciones que está haciendo su script. Por ejemplo, con un código como:
El atacante puede pasar por alto el cheque al pasar
'[a]' '[a]'
.Ahora, si no se aplica esa coincidencia de patrón ni el operador de división + glob , ¿cuál es el peligro de dejar una variable sin comillas?
Tengo que admitir que sí escribo:
Allí, las citas no perjudican pero no son estrictamente necesarias.
Sin embargo, un efecto secundario de omitir citas en esos casos (por ejemplo, en las respuestas de preguntas y respuestas) es que puede enviar un mensaje incorrecto a los principiantes:
que puede estar bien no citar variables.Por ejemplo, pueden comenzar a pensar que si
a=$b
está bien, entonces tambiénlo estaría (lo cual no está en muchos shells, ya que está en argumentos para elexport a=$b
export
comando en el contexto de la lista) o.env a=$b
¿Qué hay de
zsh
?zsh
solucionó la mayoría de esas torpezas de diseño. Enzsh
(al menos cuando no está en el modo de emulación sh / ksh), si desea dividir , globular o emparejar patrones , debe solicitarlo explícitamente:$=var
dividir y$~var
glob o para que el contenido de la variable sea tratado como un patrón.Sin embargo, la división (pero no globalización) todavía se realiza implícitamente en la sustitución de comandos sin comillas (como en
echo $(cmd)
).Además, un efecto secundario a veces no deseado de no citar variables es la eliminación de vacíos . El
zsh
comportamiento es similar a lo que puede lograr en otros shells al deshabilitar el globbing por completo (conset -f
) y dividir (conIFS=''
). Todavía en:No habrá división + glob , pero si
$var
está vacío, en lugar de recibir un argumento vacío,cmd
no recibirá ningún argumento.Eso puede causar errores (como lo obvio
[ -n $var ]
). Posiblemente eso pueda romper las expectativas y suposiciones de un script y causar vulnerabilidades, pero no puedo encontrar un ejemplo no muy descabellado en este momento).¿Y cuando se hace necesario la división + glob operador?
Sí, generalmente es cuando quieres dejar tu variable sin comillas. Pero luego debe asegurarse de sintonizar sus operadores de división y glob correctamente antes de usarlo. Si solo desea la parte dividida y no la parte glob (que es el caso la mayor parte del tiempo), entonces necesita deshabilitar globbing (
set -o noglob
/set -f
) y corregir$IFS
. De lo contrario, también causará vulnerabilidades (como el ejemplo CGI de David Korn mencionado anteriormente).Conclusión
En resumen, dejar una variable (o sustitución de comando o expansión aritmética) sin comillas en shells puede ser muy peligroso, especialmente cuando se realiza en contextos incorrectos, y es muy difícil saber cuáles son esos contextos incorrectos.
Esa es una de las razones por las que se considera una mala práctica .
Gracias por leer hasta ahora. Si se te pasa por la cabeza, no te preocupes. No se puede esperar que todos entiendan todas las implicaciones de escribir su código de la manera en que lo escriben. Es por eso que tenemos recomendaciones de buenas prácticas , para que se puedan seguir sin necesariamente entender por qué.
(y en caso de que aún no sea obvio, evite escribir código confidencial de seguridad en shells).
¡Y por favor cite sus variables en sus respuestas en este sitio!
¹En
ksh93
ypdksh
y derivados, la expansión de llaves también se realiza a menos que el globbing esté deshabilitado (en el caso deksh93
versiones hasta ksh93u +, incluso cuando labraceexpand
opción está deshabilitada).fuente
[[
, solo es necesario citar el RHS de las comparaciones:if [[ $1 = "$2" ]]; then
[[ $* = "$var" ]]
no es lo mismo que[[ "$*" = "$var" ]]
si el primer carácter de$IFS
no es espacio conbash
(y tambiénmksh
si$IFS
está vacío, aunque en ese caso no estoy seguro de qué$*
es igual, ¿debería generar un error?)).foo='bar; rm *'
, no, no lo hará, sin embargo, enumerará el contenido del directorio actual que puede contar como una divulgación de información.print $foo
Sin embargo, enksh93
(dondeprint
es el reemplazo paraecho
eso se abordan algunas de sus deficiencias) tiene una vulnerabilidad de inyección de código (por ejemplo, confoo='-f%.0d z[0$(uname>&2)]'
) (realmente necesitaprint -r -- "$foo"
.echo "$foo"
Todavía está mal y no es reparable (aunque generalmente menos dañino)).[Inspirado por esta respuesta de cas .]
Pero que si …?
No puedo decir ; eso fallará si es la cadena vacía.
grep "$ignorecase" other_grep_args
$ignorecase
Respuesta:
Como se discutió en la otra respuesta, esto todavía fallará si
IFS
contiene un-
o uni
. Si se ha asegurado de queIFS
no contiene ningún carácter en su variable (y está seguro de que su variable no contiene ningún carácter global), entonces esto probablemente sea seguro.Pero hay una forma más segura (aunque es algo fea y poco intuitiva): usar
${ignorecase:+"$ignorecase"}
. De la especificación del lenguaje de comandos de shell POSIX , en 2.6.2 Expansión de parámetros ,El truco aquí, tal como es, es que estamos usando
ignorecase
comoparameter
y"$ignorecase"
como elword
. Entonces${ignorecase:+"$ignorecase"}
significaEsto nos lleva a donde queremos ir: si la variable se establece en la cadena vacía, se "eliminará" (toda esta expresión enrevesada no se evaluará a nada , ni siquiera una cadena vacía), y si la variable tiene un valor no -valor vacío, obtenemos ese valor, citado.
Pero que si …?
Respuesta:
Puede pensar que este es un caso para usar¡No! Resista la tentación de siquiera pensar en usareval
.eval
aquí.Una vez más, si se ha asegurado de que
IFS
no contiene ningún carácter en su variable (excepto los espacios, que desea honrar), y está seguro de que su variable no contiene ningún carácter global, entonces lo anterior es probablemente seguro.Pero, si está usando bash (o ksh, zsh o yash), hay una forma más segura: use una matriz:
De bash (1) ,
Entonces se
"${criteria[@]}"
expande a (en el ejemplo anterior) los elementos cero, dos o cuatro de lacriteria
matriz, cada uno citado. En particular, si ninguna de las condiciones s es verdadera, lacriteria
matriz no tiene contenido (como lo establece lacriteria=()
instrucción) y no se"${criteria[@]}"
evalúa como nada (ni siquiera una cadena vacía inconveniente).Esto se vuelve especialmente interesante y complicado cuando se trata de varias palabras, algunas de las cuales son entradas dinámicas (del usuario), que no conoce de antemano, y pueden contener espacios u otros caracteres especiales. Considerar:
Tenga en cuenta que
$fname
se cita cada vez que se utiliza. Esto funciona incluso si el usuario ingresa algo comofoo bar
ofoo*
;"${criteria[@]}"
evalúa a-name "foo bar"
o-name "foo*"
. (Recuerde que se cita cada elemento de la matriz).Las matrices no funcionan en todos los shells POSIX; Las matrices son un ksh / bash / zsh / yash-ism. Excepto ... hay una matriz que admite todos los shells: la lista de argumentos, también conocida como
"$@"
. Si ha terminado con la lista de argumentos con la que fue invocado (por ejemplo, ha copiado todos los "parámetros posicionales" (argumentos) en variables, o los ha procesado de otra manera), puede usar la lista arg como una matriz:La
"$@"
construcción (que, históricamente, vino primero) tiene la misma semántica que : expande cada argumento (es decir, cada elemento de la lista de argumentos) a una palabra separada, como si hubiera escrito ."${name[@]}"
"$1" "$2" "$3" …
Extracto de la especificación del lenguaje de comandos de shell POSIX , en 2.5.2 Parámetros especiales ,
El texto completo es algo críptico; El punto clave es que especifica que
"$@"
generará campos cero cuando no hay parámetros posicionales. Nota histórica: cuando"$@"
se introdujo por primera vez en el shell Bourne (predecesor de bash) en 1979, tenía un error que"$@"
fue reemplazado por una sola cadena vacía cuando no había parámetros posicionales; ver ¿Qué${1+"$@"}
significa en un script de shell y en qué se diferencia"$@"
? , La familia tradicional Bourne Shell , ¿Qué${1+"$@"}
significa ... y dónde es necesario? y"$@"
contra${1+"$@"}
.Las matrices también ayudan con la primera situación:
____________________
PS (csh)
Esto debería ser evidente, pero, en beneficio de las personas que son nuevas aquí: csh, tcsh, etc., no son shells Bourne / POSIX. Son una familia completamente diferente. Un caballo de un color diferente. Un juego completamente diferente. Una raza diferente de gato. Aves de otra pluma. Y, más particularmente, una lata diferente de gusanos.
Parte de lo que se ha dicho en esta página se aplica a csh; tales como: es una buena idea citar todas sus variables a menos que tenga una buena razón para no hacerlo, y esté seguro de saber lo que está haciendo. Pero, en csh, cada variable es una matriz: sucede que casi todas las variables son una matriz de un solo elemento y actúan de manera bastante similar a una variable de shell ordinaria en shells Bourne / POSIX. Y la sintaxis es terriblemente diferente (y quiero decir terriblemente ). Entonces no diremos nada más sobre los shells de la familia csh aquí.
fuente
csh
, se prefiere utilizar$var:q
de"$var"
que este último no funciona para las variables que contienen caracteres de nueva línea (o si desea citar los elementos de la matriz de forma individual, en lugar de unirse a ellos con espacios en un solo argumento).bitrate="--bitrate 320"
funciona con${bitrate:+$bitrate}
ybitrate=--bitrate 128
no funcionará${bitrate:+"$bitrate"}
porque rompe el comando. ¿Es seguro usar${variable:+$variable}
con no"
?$@
para otra cosa), o simplemente se niega a usar una matriz por otras razones, me remito a "Como se discutió en la otra respuesta". ... (Continúa)${variable:+$variable}
sin"
) fallará si IFS contiene-
,0
,2
,3
,a
,b
,e
,i
,r
ot
.${bitrate:+$bitrate}
Era escéptico de la respuesta de Stéphane, sin embargo, es posible abusar de
$#
:o $ ?:
Estos son ejemplos artificiales, pero el potencial existe.
fuente