shellcheck recomienda no usar basename: ¿por qué?

25

Estoy probando shellcheck .

Tengo algo asi

basename "${OPENSSL}" 

y recibo la siguiente sugerencia

Use parameter expansion instead, such as ${var##*/}.

Desde el punto de vista práctico no veo diferencia

$ export OPENSSL=/opt/local/bin/openssl
$ basename ${OPENSSL}
openssl
$ echo ${OPENSSL##*/}
openssl

Como basenameestá en las especificaciones POSIX , no sé por qué debería ser la mejor práctica. Alguna pista?

Matteo
fuente
8
Bifurca un nuevo proceso cuando no es necesario.
jordanm
@jordanm es justo ... No pensé en la eficiencia.
Matteo
3
@jordanm Por otro lado, funciona con proyectiles que no sean bash
Matteo
1
@JosephR. eso es lo que pensé pero descubrí que no funciona csh. Supongo cshque no es POSIX entonces.
terdon
3
@terdon - csh está muy lejos de POSIX.
jordanm

Respuestas:

25

No se trata de eficiencia, se trata de corrección. basenameusa líneas nuevas para delimitar los nombres de archivo que imprime. En el caso habitual, cuando solo pasa un nombre de archivo, agrega una nueva línea final a su salida. Dado que los nombres de archivo pueden contener nuevas líneas, esto dificulta el manejo correcto de estos nombres de archivo.

Se complica aún más por el hecho de que la gente utiliza generalmente basenamecomo esto: "$(basename "$file")". Esto hace que las cosas aún más difícil, porque $(command)tiras todos los saltos de línea finales de command. Considere el caso poco probable que $filetermina con una nueva línea. Luego basenameagregará una nueva línea adicional, pero "$(basename "$file")"eliminará ambas líneas nuevas, dejándolo con un nombre de archivo incorrecto.

Otro problema basenamees que si $filecomienza con un -(guión aka menos), se interpretará como una opción. Este es fácil de arreglar:$(basename -- "$file")

La forma robusta de usar basenamees esta:

# A file with three trailing newlines.
file=$'/tmp/evil\n\n\n'

# Add an 'x' so we can tell where $file's newlines end and basename's begin.
file_x="$(basename -- "$file"; printf x)"

# Strip off two trailing characters: the 'x' added by us and the newline added by basename. 
base="${file_x%??}"

Una alternativa es usarla ${file##*/}, que es más fácil pero tiene errores propios. En particular, está mal en los casos donde $filees /o foo/.

Mate
fuente
2
Muy buenos puntos, +1. Me alegra que el OP haya aceptado esto en lugar del mío.
terdon
2
Contrapunto: ¿y si $filees así foo/? ¿Y si es así /?
Gilles 'SO- deja de ser malvado'
2
@Gilles: Tienes razón. Estoy empezando a pensar que el basenameenfoque es mejor, después de todo, tan hacky como es. Las mejores alternativas que se me ocurren son ${${${file}%%/#}##*/}y [[ $file =~ "([^/]*)/*$" ]] && printf "%s" $match[1], las cuales son específicas de zsh y ninguna de las cuales se maneja /correctamente.
Matt
@terdon Gracias por no haberlo tomado personalmente :-). Los archivos con líneas nuevas no son comunes, pero Matt tiene un punto. Por supuesto, usar la sustitución de variables también es más eficiente.
Matteo
16

Las líneas relevantes en shellcheckel código fuente son:

checkNeedlessCommands (T_SimpleCommand id _ (w:_)) | w `isCommand` "dirname" =
    style id "Use parameter expansion instead, such as ${var%/*}."
checkNeedlessCommands (T_SimpleCommand id _ (w:_)) | w `isCommand` "basename" =
    style id "Use parameter expansion instead, such as ${var##*/}."
checkNeedlessCommands _ = return ()

No se proporciona ninguna explicación explícitamente, pero según el nombre de la función ( checkNeedlessCommands) parece que @jordanm tiene toda la razón y sugiere que evite bifurcar un nuevo proceso.

terdon
fuente
77
Que la fuente esté contigo :)
Joseph R.
3

dirname, basename, readlinkEtc (gracias @Marco - esto se corrige) pueden crear problemas de portabilidad cuando la seguridad se vuelve importante (que requiere la seguridad de la ruta). Muchos sistemas (como Fedora Linux) lo ubican /binmientras que otros (como Mac OSX) lo ubican /usr/bin. Luego está Bash en Windows, por ejemplo, cygwin, msys y otros. Siempre es mejor mantenerse puro Bash, cuando sea posible. (por comentario de @Marco)

Por cierto, gracias por el puntero para comprobar, no he visto eso antes.

AsymLabs
fuente
1
1) ¿Qué quiere decir con "seguridad"? ¿Puedes elaborar? 2) El OP no menciona dirnameen absoluto. 3) Las utilidades básicas básicas deben estar en la RUTA, donde sea que estén almacenadas. Todavía no he visto un sistema donde basenameno estaba en la RUTA. 4) Asumir que bash está disponible es un problema de portabilidad. Siempre es mejor mantenerse alejado de bash y usar un shell POSIX cuando se requiere portabilidad.
Marco
@Marco Gracias por señalar estos problemas. Sí, tienes razón en que las utilidades están en el camino. Pero donde uno quiere proporcionar seguridad adicional a un script Bash, es una buena práctica proporcionar la ruta absoluta. Por lo tanto, llamar a '/ bin / basename' funcionará para los sistemas RedHat, pero producirá un error en una Mac. Esto se explica mejor en el Bash Cookbook, donde aproximadamente una cuarta parte de las 600 páginas están dedicadas a este tema. Hemos hecho un intento brusco de abordar los problemas de seguridad allí en nuestra fuente gratuita secure-lib .
AsymLabs
@Marco Point 4 es un comentario válido pero la pregunta (OP) comienza con y está escrita alrededor de shellcheck, que está diseñada para verificar ambos scripts sh / bash. Por lo tanto, debemos suponer que no es estrictamente Posix por defecto.
AsymLabs
@Marco La seguridad de la variable de entorno de la ruta puede verse comprometida fácilmente. Por ejemplo, me sorprendió mucho encontrar hace algunos años que el Ruby RVM, instalado localmente, por defecto colocaba su ruta antes que la ruta del sistema.
AsymLabs
El OP menciona específicamente POSIX y la pregunta está etiquetada posixy no bash. No encuentro ningún indicador de que el OP requiera una solución bash. Su afirmación "Siempre es mejor mantenerse puro Bash" es simplemente incorrecta, lo siento.
Marco