bash - Sangrado de heredocs con espacios.
indentation spaces (2)
Para el desarrollo personal y los proyectos en los que trabajo, usamos cuatro espacios en lugar de pestañas. Sin embargo, necesito usar un heredoc, y no puedo hacerlo sin romper el flujo de sangría.
La única forma de trabajar para hacer esto que puedo pensar sería esta:
usage() {
cat << '' EOF'' | sed -e ''s/^ //'';
Hello, this is a cool program.
This should get unindented.
This code should stay indented:
something() {
echo It works, yo!;
}
That''s all.
EOF
}
¿Hay una mejor manera de hacer esto?
Déjame saber si esto pertenece en el Unix / Linux Stack Exchange en su lugar.
(Si está utilizando bash
4, desplácese hasta el final para ver cuál es la mejor combinación de shell puro y legibilidad).
Para los scripts de shell, el uso de pestañas no es una cuestión de preferencia o estilo; Es como se define el lenguaje.
usage () {
# Lines between EOF are each indented with the same number of tabs
# Spaces can follow the tabs for in-document indentation
cat <<-EOF
Hello, this is a cool program.
This should get unindented.
This code should stay indented:
something() {
echo It works, yo!;
}
That''s all.
EOF
}
Otra opción es evitar un documento aquí, al costo de tener que usar más citas y continuaciones de línea:
usage () {
printf ''%s/n'' /
"Hello, this is a cool program." /
"This should get unindented." /
"This code should stay indented:" /
" something() {" /
" echo It works, yo!" /
" }" /
"That''s all."
}
Si está dispuesto a renunciar a la compatibilidad con POSIX, puede usar una matriz para evitar las continuaciones de línea explícitas:
usage () {
message=(
"Hello, this is a cool program."
"This should get unindented."
"This code should stay indented:"
" something() {"
" echo It works, yo!"
" }"
"That''s all."
)
printf ''%s/n'' "${message[@]}"
}
Lo siguiente usa un documento aquí de nuevo, pero esta vez con el comando readarray
bash
4 para readarray
una matriz. La expansión de parámetros se encarga de eliminar un número fijo de espacios desde el principio de cada mentira.
usage () {
# No tabs necessary!
readarray message <<'' EOF''
Hello, this is a cool program.
This should get unindented.
This code should stay indented:
something() {
echo It works, yo!;
}
That''s all.
EOF
# Each line is indented an extra 8 spaces, so strip them
printf ''%s'' "${message[@]# }"
}
Una última variación: puede usar un patrón extendido para simplificar la expansión del parámetro. En lugar de tener que contar cuántos espacios se utilizan para la sangría, simplemente finalice la sangría con un carácter no espaciado elegido, luego haga coincidir el prefijo fijo. Yo uso :
(El espacio que sigue a los dos puntos es para facilitar la lectura; se puede eliminar con un cambio menor en el patrón de prefijo).
(Además, como inconveniente, un inconveniente de su excelente truco de usar un delimitador de aquí-doc que comienza con espacios en blanco es que le impide realizar expansiones dentro de aquí-doc. Si quisiera hacerlo, habría para dejar el delimitador sin sangrar, o hacer una pequeña excepción a su regla de no tabulación y usar <<-EOF
y un delimitador de cierre con sangría de tabulación).
usage () {
# No tabs necessary!
closing="That''s all"
readarray message <<EOF
: Hello, this is a cool program.
: This should get unindented.
: This code should stay indented:
: something() {
: echo It works, yo!;
: }
: $closing
EOF
shopt -s extglob
printf ''%s'' "${message[@]#+( ): }"
shopt -u extglob
}
geta() {
local _ref=$1
local -a _lines
local _i
local _leading_whitespace
local _len
IFS=$''/n'' read -rd '''' -a _lines ||:
_leading_whitespace=${_lines[0]%%[^[:space:]]*}
_len=${#_leading_whitespace}
for _i in "${!_lines[@]}"; do
printf -v "$_ref"[$_i] ''%s'' "${_lines[$_i]:$_len}"
done
}
gets() {
local _ref=$1
local -a _result
local IFS
geta _result
IFS=$''/n''
printf -v "$_ref" ''%s'' "${_result[*]}"
}
Este es un enfoque ligeramente diferente que requiere Bash 4.1 debido a la asignación de printf a los elementos de la matriz. (Para versiones anteriores, sustituya la función geta
continuación). Se trata de espacios en blanco iniciales arbitrarios, no solo de una cantidad predeterminada.
La primera función, geta
, lee desde stdin, elimina los espacios en blanco iniciales y devuelve el resultado en la matriz cuyo nombre se pasó.
El segundo, gets
, hace lo mismo que geta
pero devuelve una sola cadena con nuevas líneas intactas (excepto la última).
Si pasa el nombre de una variable existente a geta
, asegúrese de que ya esté vacío.
Invoca geta
así:
$ geta hello <<''EOS''
> hello
> there
>EOS
$ declare -p hello
declare -a hello=''([0]="hello" [1]="there")''
gets
:
$ unset -v hello
$ gets hello <<''EOS''
> hello
> there
> EOS
$ declare -p hello
declare -- hello="hello
there"
Este enfoque debería funcionar para cualquier combinación de caracteres de espacios en blanco iniciales, siempre que sean los mismos caracteres para todas las líneas subsiguientes. La función elimina el mismo número de caracteres del frente de cada línea, en función del número de caracteres de espacio en blanco iniciales en la primera línea.
La razón por la que todas las variables comienzan con un guión bajo es para minimizar la posibilidad de una colisión de nombres con el nombre de la matriz pasada. Es posible que desee volver a escribir esto para prefijarlos con algo incluso menos propenso a chocar.
Para usar en la función de OP:
gets usage_message <<''EOS''
Hello, this is a cool program.
This should get unindented.
This code should stay indented:
something() {
echo It works, yo!;
}
That''s all.
EOS
usage() {
printf ''%s/n'' "$usage_message"
}
Como se mencionó, para Bash mayores de 4.1:
geta() {
local _ref=$1
local -a _lines
local _i
local _leading_whitespace
local _len
IFS=$''/n'' read -rd '''' -a _lines ||:
_leading_whitespace=${_lines[0]%%[^[:space:]]*}
_len=${#_leading_whitespace}
for _i in "${!_lines[@]}"; do
eval "$(printf ''%s+=( "%s" )'' "$_ref" "${_lines[$_i]:$_len}")"
done
}