[Bash][Awk] variable dans array

Mon problème est le suivant :
Je lis le contenu d’un fichier, alias /etc/default/sslh, pour récupérer la ligne concernant les options du service, soit celle-ci :

Est-il possible avec awk d’obtenir son contenu … oui.

me restitue bien :

 --user sslh --listen 192.168.xyz.abc:443 --ssh 127.0.0.1:22 --ssl 127.0.0.1:443 --pidfile /var/run/sslh/sslh.pid 

On doit même pouvoir concaténer les deux … non ?!

Ce que j’aimerais arrivé à faire est que, dès qu’il détecte un espace, cela remplisse une nouvelle variable.

Soit un tableau associatif, nommé SSLH_INFOS, par exemple …
Quand le script lit que l’écriture commence par ‘–’, il créé la variable, tel que SSLH_INFOS[“user”], par exemple, et que l’écriture suivante devienne sa valeur !

Oui, je sais, c’est un peu complexe … mais je serais étonné que cela ne soit pas faisable.
Quoiqu’il en soit, je tiens à ce que ce soit du Bash, et pas un autre langage de script :wink:


Devient :
awk -F '"' '/DAEMON_OPTS/ { print $2 }' /etc/default/sslh


Je n’y crois pas, j’y suis arrivé !!!

#!/bin/bash
#set -x
declare -A SSLH_INFOS
cmd="$(awk -F '"' '/DAEMON_OPTS/ { print $2 }' /etc/default/sslh)"
for element in ${cmd// / }; do
    if grep -q '^--' <<< "${element}"; then
    var="${element:2}"
    SSLH_INFOS+=( [${var}]="" )
fi

if grep -q -v '^--' <<< "${element}"; then
    SSLH_INFOS+=( [${var}]="${element}" )
fi
done
echo "${SSLH_INFOS["user"]}"

Champagne ! :tongue:

salut,

[code]$ awk_output=’–user sslh --listen 192.168.147.3:443 --ssh 127.0.0.1:247 --ssl 127.0.0.1:443 --pidfile /var/run/sslh/sslh.pid’
$ declare -A sslh_infos
$ for i in $awk_output

do
[[ “$i” =~ ^-- ]] && index="${i##*-}" || sslh_infos["$index"]="$i"
done
$
$ for i in “${!sslh_infos[@]}”; do echo “$i : ${sslh_infos[”$i"]}"; done
pidfile : /var/run/sslh/sslh.pid
user : sslh
ssl : 127.0.0.1:443
ssh : 127.0.0.1:247
listen : 192.168.147.3:443[/code]ça marchera tant que les paramètres des options ne contiendront pas d’espaces. :confused:

On est tout à fait d’accord.
C’est même le propos. L’auteur de SSLH l’a voulu ainsi ; un jour cela pourra peut-être changé.

Merci pour ton code. C’est de la concision, çà ! Bien…

En effet, ça fonctionne !

cmd="$(awk -F '"' '/DAEMON_OPTS/ { print $2 }' /etc/default/sslh)"""

for i in ${cmd// / }; do
    [[ "$i" =~ ^-- ]] && index="${i##*-}" || SSLH_INFOS["${index}"]="$i"
done

Maintenant, si je fais :

for i in $(awk -F '"' '/DAEMON_OPTS/ { print $2 }' /etc/default/sslh); do 
    [[ "$i" =~ ^-- ]] && index="${i##*-}" || SSLH_INFOS["${index}"]="$i"
done

Shellcheck m’informe de cette erreur SC2013 : de préférer à l’écriture ci-dessus, l’usage d’une boucle while …

Ça tombe bien, j’aime les boucles while :p, sauf que si j’écris ça :

while IFS=" " read -r i ; do
    echo "i: $i"
    [[ "$i" =~ ^-- ]] && index="${i##*-}" || SSLH_INFOS["${index}"]="$i"
done < <(awk -F '"' '/DAEMON_OPTS/ { print $2 }' /etc/default/sslh)

Cela ne fonctionne pas, parce que - logiquement - cela retourne ligne par ligne, dans son ensemble - comme il n’y en a qu’une, cela affecte à i l’ensemble de la ligne.
Hors, j’aurais cru que l’emploi de la variable IFS, filtrant sur une espace aurait fonctionné … et, bien non !


Après il est possible d’écrire ainsi :

while IFS=" " read -r  var1 val1 var2 val2 var3 val3 ; do

Mais sincèrement ca devient trop complexe, à la fois pour l’usage que j’en ai … et puis si le projet SSLH est utilisé pour filtrer aussi du VPN, voire autre flux qu’il est capable de multiplexer, et donc paramétré, c’est dans cette écriture vite ingérable !


Bref, cela m’aura permis de réviser un peu mon Bash : http://mywiki.wooledge.org/BashFAQ/001
Et, de voir certaines écritures intéressantes, telle que la 6ème pour obtenir les infos à partir du fichier /etc/passwd. :smiley:

normalement, on ne boucle pas avec for sur une substitution de commandes, parce que ça coupe les lignes en mots.
mais, ici, c’est ce qu’on veut faire : boucler sur chaque mot !

si tu tiens à le faire avec une boucle while, c’est le delimiter de la commande read qu’il faut assigner à espace.

$ phrase='shellcheck est un automate imbécile' $ while read -d ' ' w; do echo "$w"; done <<<"$phrase"; echo "$w" #sinon il manquera un mot ! comme quoi, ce n'est peut-être pas la meilleure méthode. shellcheck est un automate imbécile $

1 J'aime

Je te remercie profondèment de ton intervention ; en effet, ce code fonctionne correctement :

while read -r -d ' ' world; do
    [[ "${world}" =~ ^-- ]] && index="${world##*-}" || SSLH_INFOS["${index}"]="${world}"
done < <(awk -F '"' '/DAEMON_OPTS/ { print $2 }' /etc/default/sslh)

:clap:


Maintenant, à savoir ce qui est plus efficace, efficient, entre la boucle find que tu as écrite, et ce while …
Faut que je me renseigne là-dessus, moi !

  • Ma première boucle find: real 0m0.084s
  • Pour la while: real 0m0.021s
  • Pour la find que tu nous a montré : real 0m0.016s

Et, quand même … :wind_blowing_face:
Comme quoi, les choses les plus simples sont les meilleurs !

Sinon, pour les amateurs de one liner, il y avait aussi ceci qui comprend l’extraction de la ligne du fichier /etc/default/sslh:

# one liner
#
eval "declare -A sslh_info=("$(awk 'BEGIN{FS="\"?--|\""}/^DAEMON_OPTS/{for(i=2;i<NF;i++){split($i,a," "); printf "[%s]=%s ", a[1],a[2]}}' /etc/default/sslh)")"
#
# vérification
#
for i in "${!sslh_info[@]}"; do printf "%-10s%s %s\n" "$i" ":" "${sslh_info[$i]}";done
#
# retourne:
#
pidfile   : /var/run/sslh/sslh.pid
user      : sslh
ssl       : 127.0.0.1:443
ssh       : 127.0.0.1:247
listen    : 192.168.147.3:443

Exercice de style peu lisible, je vous l’accorde.

EDIT
one liner courte encore ( et sans eval. eval=evil!):

declare $(awk ‘BEGIN{FS=""?–|""}/^DAEMON_OPTS/{for(i=2;i<NF;i++){split($i,a," "); print "sslh_info[“a[1]”]="a[2]}}’ /etc/default/sslh)

declare -A $(awk 'BEGIN{FS="\"?--|\""}/^DAEMON_OPTS/{for(i=2;i<NF;i++){split($i,a," "); print "sslh_info["a[1]"]="a[2]}}' /etc/default/sslh)
1 J'aime

Ahhh, tu me rassures ! :stuck_out_tongue:
C’est pour cette raison que j’ai mis :heart_eyes: <3, après ta correction. :wink:


Temps d’exécution :

real 0m0.017s

Et bien non, mon edit plus haut ne marche pas pour les array associatives. Je corrige mon post. Intégrer du bash dans du awk est toujours pénible. J’essaye, en général de tout faire en awk quand c’est possible.

Moi aussi de plus en plus, j’aime les possibilités qu’il offre, même si je suis très loin de le maîtriser !

Voilà j’ai corrigé mon edit plus haut. Ça devrait marcher maintenant (et sans eval!).

J’ai vomi. Et pas parce que je suis content :frowning:.

T’es un peu dur, là, non ?
Faut avouer l’exercice de style est là, quand même …

Bon, après je ne comprends pas comment ça fait pour attribuer au tableau associatif.
Mais, c’est sidérant ce qu’on peut faire.


Allez, “miam-time”. Bon app, all

C’était juste une boutade mais OK, je développe :slight_smile:.
Oui c’est effectivement un exercice de style. C’est simplement que je souscris nettement plus à la philosophie qui dit que le code est fait d’abord pour être lu par des humains, puis pour être exécuté par une machine. Et ce genre de one-liners est diamétralement opposé. C’est plutôt la philosophie du “tant que ça marche…”.

En plus lisible (mais, faut un peu connaître awk évidemment)

#!/bin/bash

declare -A $(awk '
  BEGIN{
    FS="\"?--|\""
  }
  /^DAEMON_OPTS/{
    for(i=2;i<NF;i++){
      split($i,a," ")
      print "sslh_info["a[1]"]="a[2]
      }
  }' /etc/default/sslh
)
#
# Check
for i in "${!sslh_info[@]}"; do
  printf "%-10s%s %s\n" "$i" ":" "${sslh_info[$i]}"
done;
unset sslh_info

Ce code est valide, efficace et évite un certain nombre de sub-shell (process) comme avec ce que j’ai vu plus haut awk | awk | grep | traitement bash

Est-ce que la notation bash [ condition ] && code si oui || code sinon te rend malade aussi?

Arrête de faire mumuse, et “ne vous battez pas les nenfants” :wink:

Perso : non.
Même s’il me semble avoir lu, dans un de mes books sur Bash, qu’il vaut mieux éviter pour faciliter la compréhension, la lecture.
Perso, je m’y suis fait :wink:

Ensuite, étant donné que le temps de traitement est sensiblement le même que la boucle find écrite par @Watael - je préfère car, elle je la comprends.

J’arrive à lire ton instruction oneliner , mais je ne comprends pas comment il attribue le tableau associatif, le construit, puis lui affecte ses valeurs … alors que je comprends la boucle for, l’instruction split, ainsi que le BEGIN - celui-ci, je n’aurais pas eu l’idée de l’écrire ainsi. Et, pourquoi tu démarres la variable i à 2 ?!

Donc, pour moi, oui, c’est puissant, je trouve cela “beau” qu’awk soit capable de faire un tel traitement, mais si je ne le comprends pas pleinement, ça m’embête. De fait, je vais avoir tendance à utiliser quelque chose que je comprends, qui m’es clair.
Même si - pour l’exemple - pendant longtemps, l’instruction if … else abrégé m’a posé soucis. Ce n’est plus un problème … à force de coder. Donc, je ne me soucis pas, il y aura un temps, où je serais capable de comprendre ton code oneliner. :stuck_out_tongue:

Autre point : ton code oneliner créé le tableau associatif … soit, mais si le tableau est déclaré auparavant dans un autre fichier, c’est d’ailleurs dans cette idée que je l’ai créé en première instruction.
Comment tu résous le problème ?

[quote=“seb-ksl, post:16, topic:68927”]
C’est simplement que je souscris nettement plus à la philosophie qui dit que le code est fait d’abord pour être lu par des humains[…][/quote]

à ce compte là, on n’est pas près de te voir coder en sed.

C’est ce qui compte car finalement c’est toi qui va devoir en faire la maintenance. Cela dit, rien ne t’empêche d’optimiser en supprimant les sous-process (pipe inutiles, awk puis grep - ou l’inverse - sans oublier le fameux UUOC etc… Le one liner que je propose évite bon nombre de ces problèmes.

Le code awk imprime simplement ceci:
sslh_info[user]=sslh sslh_info[listen]=192.168.147.3:443 sslh_info[ssh]=127.0.0.1:247 sslh_info[ssl]=127.0.0.1:443

L’instruction declare -A $(code awk) revient à faire:
declare -A sslh_info[user]=sslh sslh_info[listen]=192.168.147.3:443 sslh_info[ssh]=127.0.0.1:247 sslh_info[ssl]=127.0.0.1:443

Enfin, mon code “aéré” reste un one liner mais sur plusieurs lignes pour faciliter la lecture. One liner ne veut pas nécessairement dire obfuscated code

1 J'aime

Effectivement, pas plus qu’en Perl ;-).