| Jacques's profilejanelPhotosBlogLists | Help |
|
October 24 Retrouver ses petits avec select-stringComme moi, vous avez des fichiers texte dont vous devez analyser le contenu : retrouver un message spécifique dans une série de fichiers log, vérifier la présence éventuelle d’un texte répondant à un schéma particulier, savoir quels fichier(s) contiennent l’information et où. Dans une invite de commande standard, vous penseriez tout de suite à la commande findstr, et vous auriez raison. Cette commande est toujours accessible sous Windows PowerShell, mais il est également possible d’utiliser les commandes natives de PowerShell, notamment la commandelette select-string.
Imaginons que je travaille dans le répertoire Windows et que je fasse des recherches sur le contenu des fichiers INI. Je veux par exemple retrouver le fichier qui contient la section [drivers]. Avec findstr j’écrirai :
Windows[33/58]> findstr /l "[drivers]" *.ini
system.ini:[drivers]
[Aparté] pour ceux qui se demandent ce que veut dire Windows[33/58]>, c’est juste mon prompt. Il m’indique que je travaille dans le répertoire Windows (dont le chemin d’accès complet est rappelé dans la barre de titre de la console), et que ce répertoire contient 33 fichiers et 58 sous-répertoires.
[/Aparté]
[Aparté2] Le paramètre /l (pour littéral) ordonne à findstr de rechercher le texte littéral. Sinon, findstr traitera le texte comme une expression régulière. Vous ne verrez sans doute pas de différence dans la plupart des situations, mais l’exemple ci-dessus ne marchera pas si j’enlève le paramètre /l. Essayez, vous verrez la différence. Cela tient au fait que les crochets sont des éléments de syntaxe d’une expression régulière. En l’occurrence, l’expression régulière “[drivers]” retourne toutes les lignes qui contiennent au moins un des caractères entre crochets, soit d, r, i, v, e ou s. N’hésitez pas à consulter la documentation en ligne de Microsoft sur le sujet.
[/Aparté2]
Avec select-string on pourra écrire de la même façon :
Windows[33/58]> select-string "[drivers]" *.ini -simple
system.ini:9:[drivers]
Le résultat est sensiblement identique à celui produit par findstr, à ceci près que select-string renvoie automatiquement le numéro de ligne correspondant. On pourrait également l’obtenir avec findstr en ajoutant le paramètre /n.
Notez pour votre information que la valeur “[drivers]” est en fait passée au paramètre -pattern et que la valeur *.ini est passée au paramètre –path. Ces deux paramètres ont les positions 1 et 2 par défaut, d’où la possibilité de les nommer ou pas. Le paramètre -simple a pour nom complet -simpleMatch, et vous l’aurez déjà compris il correspond au /l de findstr.
Plutôt que de préciser la liste des fichiers dans la syntaxe de select-string, on pourra être amené à récupérer cette liste avec une autre commande (ou une série d’autres commandes) et à rediriger cette liste vers select-string :
Windows[33/58]> dir *.ini | select-string "[drivers]" -simple
system.ini:9:[drivers]
Bon, mais essayons d’aller un peu plus loin en retrouvant la liste de toutes les sections contenues dans ces fichiers. Le nom d’une section se caractérise par le fait de commencer par un crochet ouvrant et de terminer par un crochet fermant, tout ce qui se trouve entre les deux étant le nom de la section. On pourrait taper simplement :
Windows[33/58]> dir *.ini | select-string "[" -simple
Malheureusement, cela va retourner toutes les lignes qui contiennent un crochet ouvrant, qu’il soit placé en début de ligne ou pas. Essayez, et vous verrez que de nombreuses lignes parasitent le résultat. Il faut donc pouvoir forcer la détection du crochet ouvrant en tant que premier caractère de la ligne. Une expression régulière saura très bien faire cela (voir la documentation en ligne déjà citée) :
Windows[33/58]> dir *.ini | select-string "^\["
msdfmap.ini:18:[connect default]
msdfmap.ini:22:[sql default]
msdfmap.ini:26:[connect CustomerDatabase]
msdfmap.ini:30:[sql CustomerById]
msdfmap.ini:33:[connect AuthorDatabase]
msdfmap.ini:37:[userlist AuthorDatabase]
msdfmap.ini:40:[sql AuthorById]
ODBC.INI:1:[ODBC 32 bit Data Sources]
ODBC.INI:3:[Visio Database Samples]
SMSCFG.ini:1:[SMS MultiBoot Configuration]
SMSCFG.ini:3:[Configuration - Client Properties]
system.ini:2:[386Enh]
system.ini:9:[drivers]
system.ini:13:[mci]
vbaddin.ini:1:[Add-Ins32]
win.ini:2:[fonts]
win.ini:3:[extensions]
win.ini:4:[mci extensions]
win.ini:5:[files]
win.ini:6:[Mail]
win.ini:13:[MCI Extensions.BAK]
Ok, super. Maintenant que j’ai le résultat qui m’intéresse, je voudrais pouvoir l’afficher différemment, voire le garder en mémoire sans forcément l’afficher tout de suite. Je vais donc inspecter l’objet qui résulte de la commandelette select-string :
Windows[33/58]> dir *.ini | select-string "^\[" | get-member
TypeName: Microsoft.PowerShell.Commands.MatchInfo
Name MemberType Definition
---- ---------- ----------
Equals Method System.Boolean Equals(Object obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
get_Filename Method System.String get_Filename()
get_IgnoreCase Method System.Boolean get_IgnoreCase()
get_Line Method System.String get_Line()
get_LineNumber Method System.Int32 get_LineNumber()
get_Path Method System.String get_Path()
get_Pattern Method System.String get_Pattern()
RelativePath Method System.String RelativePath(String directory)
set_IgnoreCase Method System.Void set_IgnoreCase(Boolean value)
set_Line Method System.Void set_Line(String value)
set_LineNumber Method System.Void set_LineNumber(Int32 value)
set_Path Method System.Void set_Path(String value)
set_Pattern Method System.Void set_Pattern(String value)
ToString Method System.String ToString(), System.String ToString(String directory)
Filename Property System.String Filename {get;}
IgnoreCase Property System.Boolean IgnoreCase {get;set;}
Line Property System.String Line {get;set;}
LineNumber Property System.Int32 LineNumber {get;set;}
Path Property System.String Path {get;set;}
Pattern Property System.String Pattern {get;set;}
MSDN ScriptMethod System.Object MSDN();
Je note par exemple les propriétés Filename, Line et Linenumber qui correspondent certainement aux valeurs utilisées pour l’affichage par défaut. Essayons :
Windows[33/58]> select-string "^\[" *.ini | format-table filename,linenumber,line -auto
Filename LineNumber Line
-------- ---------- ----
msdfmap.ini 18 [connect default]
msdfmap.ini 22 [sql default]
msdfmap.ini 26 [connect CustomerDatabase]
msdfmap.ini 30 [sql CustomerById]
msdfmap.ini 33 [connect AuthorDatabase]
msdfmap.ini 37 [userlist AuthorDatabase]
msdfmap.ini 40 [sql AuthorById]
ODBC.INI 1 [ODBC 32 bit Data Sources]
ODBC.INI 3 [Visio Database Samples]
SMSCFG.ini 1 [SMS MultiBoot Configuration]
SMSCFG.ini 3 [Configuration - Client Properties]
system.ini 2 [386Enh]
system.ini 9 [drivers]
system.ini 13 [mci]
vbaddin.ini 1 [Add-Ins32]
win.ini 2 [fonts]
win.ini 3 [extensions]
win.ini 4 [mci extensions]
win.ini 5 [files]
win.ini 6 [Mail]
win.ini 13 [MCI Extensions.BAK]
Je peux utiliser ces propriétés pour trier, filtrer, mesurer les résultats retournés par select-string. Quelques opérations à titre d’exemple, en commençant par l’affectation d’un select-string à une variable :
Windows[33/58]> $sections = select-string "^\[" *.ini
Windows[33/58]> $sections.length
21
Windows[33/58]> $sections | where {$_.line -match "ext"}
win.ini:3:[extensions]
win.ini:4:[mci extensions]
win.ini:13:[MCI Extensions.BAK]
Windows[33/58]> $sections | group filename | sort count -desc
Count Name Group
----- ---- -----
7 msdfmap.ini {msdfmap.ini, msdfmap.ini, msdfmap.ini, msdfmap.ini...}
6 win.ini {win.ini, win.ini, win.ini, win.ini...}
3 system.ini {system.ini, system.ini, system.ini}
2 ODBC.INI {ODBC.INI, ODBC.INI}
2 SMSCFG.ini {SMSCFG.ini, SMSCFG.ini}
1 vbaddin.ini {vbaddin.ini}
Windows[33/58]> $sections | where {$_.filename -eq "win.ini"} | foreach {$_.line}
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
[MCI Extensions.BAK]
Tout cela n’est qu’une brève introduction aux possibilités de select-string. Je vous laisse le plaisir d’explorer cette commandelette au gré de vos besoins.
Janel
October 23 PowerShell, $null et les crashesAujourd’hui, petit exercice de récupération de certains évènements du journal système. En l’occurrence le script ci-dessous, que j’appellerai get-crash.ps1, récupère les évènements 6008 correspondant à une interruption inopinée du système. Le script ajoute une propriété CrashDateTime aux objets évènements retournés, dont la valeur est déterminée par la date et l’heure données dans le corps du message de l’évènement.
$crashes = get-eventlog -logname system | where {$_.eventid -eq 6008}
foreach ($crash in $crashes) {
$start = $crash.message.indexof("shutdown at ") + "shutdown at ".length
$end = $crash.message.indexof("was unexpected") - 1
$crashdatetime = $crash.message.substring($start, $end - $start)
add-member -input $crash -type NoteProperty -name CrashDateTime -value $crashdatetime -passthru
}
On pourra l’utiliser comme ceci:
PS> get-crash | ft timewritten, crashdatetime
TimeWritten CrashDateTime
----------- -------------
18/10/2006 12:15:07 12:13:57 on 18/10/2006
10/10/2006 12:11:56 12:10:43 on 10/10/2006
09/10/2006 08:31:32 08:29:33 on 09/10/2006
Bon, le but de cet exercice n’est pas vraiment de montrer comment traiter les évènements système de Windows dans PowerShell (encore que ça puisse constituer un bon point de départ). A l’évidence mon script n’est pas exploitable tel quel ; il repose sur un contenu de message en anglais, il faudrait donc l’adapter au langage de chaque système à interroger. De plus, l’usage d’une variable CrashDateTime sous forme d’une chaîne de caractères ne constitue pas une bonne pratique, il aurait fallu que mon script convertisse le contenu en une valeur exploitable en tant que System.DateTime pour, par exemple, calculer le temps écoulé entre l’heure du crash et le redémarrage du système. Je vous laisse le soin de compléter si besoin est.
Non, le véritable but de l’exercice était de voir ce qui se passe si le journal des évènements système ne contient aucun crash (pas de sarcasme s’il vous plaît au fond de la salle). Si votre système n’a jamais eu la joie de s’interrompre de manière inopinée, vous pouvez voir ce que donne le script ci-dessus. Si votre journal système contient au moins une entrée 6008 vous pouvez faire le test avec cet autre script qui se contente d’afficher les évènements dont l’ID est passé en argument :
$eventid = $args[0]
$events = get-eventlog -logname system | where {$_.eventid -eq $eventid}
foreach ($event in $events) {
"Evènement n°$($event.index), daté du $($event.timegenerated):"
$event.message
"(source: $($event.source))`n"
}
PS> get-event 6008
Evènement n°2727, daté du 10/18/2006 12:15:07:
The previous system shutdown at 12:13:57 on 18/10/2006 was unexpected.
(source: EventLog)
Evènement n°1042, daté du 10/10/2006 12:11:56:
The previous system shutdown at 12:10:43 on 10/10/2006 was unexpected.
(source: EventLog)
Evènement n°800, daté du 10/09/2006 08:31:32:
The previous system shutdown at 08:29:33 on 09/10/2006 was unexpected.
(source: EventLog)
Que se passe-t-il si je passe un ID non présent dans le journal système ?
PS> get-event 9999
Evènement n°, daté du :
(source: )
Berk. La boucle foreach a été éxécutée alors qu’il n’y avait aucun évènement à traiter. On aurait préféré que foreach prenne en compte le fait que $events était vide et ne fasse rien. Alors, est-ce un bug de PowerShell ? Et d’abord, $events était-il vraiment vide ? Vérification :
PS> . .\get-event 9999
Evènement n°, daté du :
(source: )
PS> $events
PS> $events -eq $null
True
Ah. Donc, $events a reçu la valeur $null, ce qui pour PowerShell n’est pas pareil que de n’avoir aucune valeur. Il ne s’agit pas d’un bug mais d’un choix tout à fait défendable ; je vous renvoie à la littérature existante ou à venir sur le sujet (voir notamment le livre à paraître de Bruce Payette chez Manning). Pour résumer, PowerShell considère $null comme une valeur scalaire, et si je passe $null à une boucle de traitement elle s’exécutera pour traiter $null.
Alors, comment puis-je indiquer à PowerShell que je n’ai *aucun* résultat, et qu’il ne faut donc pas exécuter la boucle de traitement ? La solution consiste à utiliser une liste vide :
PS> $liste = @()
PS> $liste | foreach {"élément = "+$_}
PS>
Vous pouvez le constater si vous faites le test, la boucle foreach ne s’exécute pas du tout. En effet, PowerShell n’a rien à transmettre car la liste est vraiment vide. Comment adapter ce principe à mes scripts ? Il suffit de forcer PowerShell à considérer le résultat de la recherche comme une liste. Si la recherche ne retourne aucun évènement, la liste sera vide.
$eventid = $args[0]
$events = @(get-eventlog -logname system | where {$_.eventid -eq $eventid})
foreach ($event in $events) {
"Evènement n°$($event.index), daté du $($event.timegenerated):"
$event.message
"(source: $($event.source))`n"
}
PS> get-event 9999
PS>
Cette conversion forcée (soulignée en rouge dans l'exemple ci-dessus) peut s’appliquer au script get-crash.ps1, ainsi qu’à tout script ou toute fonction qui veut s’assurer de ne traiter des objets que s’il en existe au moins un.
Janel October 20 Jeu, set et -matchDans mon billet publié hier soir, j’ai utilisé à deux reprises la commandelette where-object (ou plutôt son alias where) pour filtrer une liste d’objets selon leur concordance avec un terme donné. Dans les deux cas j’aurais pu simplifier la syntaxe en utilisant l’opérateur –match à la place du filtre:
Cas 1
PS> $citations | where {$_ -match “bonheur”}
On n'échappe pas au spectacle du bonheur.
Fin de citation
PS>
PS> $citations –match “bonheur”
On n'échappe pas au spectacle du bonheur.
Fin de citation
Cas 2
PS> (get-help filesystem).dynamicparameters.dynamicparameter | where {$_.name -eq "Delimiter"}
Type : @{Name=System.String}
CmdletSupported : Get-Content
PossibleValues :
Name : Delimiter
Description : Specifies the delimiter to use when reading the file. The default is "\n" (end of line).
PS>
PS> (get-help filesystem).dynamicparameters.dynamicparameter -match "Delimiter"
Type : @{Name=System.String}
CmdletSupported : Get-Content
PossibleValues :
Name : Delimiter
Description : Specifies the delimiter to use when reading the file. The default is "\n" (end of line).
Attention, dans le deuxième cas je remplace avec succès –eq par –match, mais il y a bien une différence entre –eq et –match. L’opérateur –eq vérifie la concordance exacte entre deux termes (à l’exception de la casse majuscules/minuscules qui n’est pas prise en compte par défaut : utiliser –ceq pour une prise en compte de la casse), alors que l’opérateur –match vérifie que le terme de droite est une expression régulière qui s’applique au terme de gauche.
PS> "hello" -eq "Hello"
True
PS> "hello world" -eq "Hello"
False
PS> "hello world" -match "Hello"
True
Egalement, vous remarquerez que dans la première syntaxe de mon Cas 2 je limitais le filtrage au contenu de la propriété Name du DynamicParameter, alors que la seconde syntaxe « simplifiée » cherche une correspondance avec toutes les propriétés qui peuvent être traitées en tant que System.String. On peut ainsi étendre le champ de ses recherches et découvrir des choses intéressantes, ce qui est mon cas à l’instant:
PS> (get-help filesystem).dynamicparameters.dynamicparameter -match "get-content"
Type : @{Name=Microsoft.PowerShell.Commands.FileSystemCmdletProviderEncoding}
CmdletSupported : Add-Content, Get-Content, Set-Content
PossibleValues : @{PossibleValue=System.Management.Automation.PSObject[]}
Name : Encoding
Description :
Type : @{Name=System.String}
CmdletSupported : Get-Content
PossibleValues :
Name : Delimiter
Description : Specifies the delimiter to use when reading the file. The default is "\n" (end of line).
Type : @{Name=System.Management.Automation.SwitchParameter}
CmdletSupported : Get-Content
PossibleValues : @{PossibleValue=@{Value=; Description=System.Management.Automation.PSObject[]}}
Name : Wait
Description : Waits for content to be appended to the file. If content is appended, it returns the appended content
. If the content has changed, it returns the entire file.
When waiting, Get-Content checks the file once in each second until you interrupt it, such as by pres
sing Ctrl + C.
A vous de jouer !
Janel October 19 get-content, les fins de ligne et les paramètres dynamiquesLorsqu’on lit un fichier texte avec la commandelette get-content, on obtient une table avec un enregistrement par ligne. Par exemple :
PS> set-content liste.txt @"
>> Beurre
>> Café
>> Epinards
>> Poisson
>> Allumettes
>> "@
>>
PS> $liste = get-content liste.txt
PS> $liste.length
5
PS> $liste[0]
Beurre
PS> $liste[-1]
Allumettes
Maintenant, quid de la segmentation d’un fichier selon un autre critère ? Autrement dit, peut-on personnaliser le délimiteur de fin de ligne ? A priori, une recherche dans l’aide en ligne de get-content ne donne rien qui puisse répondre à ce besoin. Et pourtant :
PS> get-content fonctions.ps1
function addition {
return $args[0] + $args[1]
}
function soustraction {
return $args[0] - $args[1]
}
function multiplication {
return $args[0] * $args[1]
}
function division {
return $args[0] / $args[1]
}
PS> $fn = get-content -delimiter "}" fonctions.ps1
PS> $fn[0]
function addition {
return $args[0] + $args[1]
}
Encore plus fort J :
PS> get-content citations.txt
On n'échappe pas au spectacle du bonheur.
Fin de citation
Et à quoi bon exécuter des projets, puisque le projet est en lui-même une jouissance suffisante?
Fin de citation
"Vous êtes libre ce soir - Oui, mais permettez-moi de le rester."
Fin de citation
PS> $citations = get-content -delimiter "Fin de citation" citations.txt
PS> $citations | where {$_ -match “bonheur”}
On n'échappe pas au spectacle du bonheur.
Fin de citation
Vous l’aurez peut-être compris, le délimiteur est un System.String, on peut donc passer n’importe quelle chaîne de caractères, et pas simplement un seul caractère. Attention, la valeur passée est sensible à la casse. Dans l’exemple ci-dessus, « Fin de citation » est différent de « fin de citation ».
Alors, d’où vient ce paramètre –delimiter qui ne figure pas dans l’aide de get-content ? La réponse se trouve dans l’aide du provider FileSystem. Si vous tapez « get-help FileSystem », vous verrez la partie concernant ce paramètre tout à la fin du très long texte. Ce paramètre étant un « paramètre dynamique » fourni par le provider, on peut également accéder à sa description ainsi :
PS> (get-help filesystem).dynamicparameters.dynamicparameter | where {$_.name -eq "Delimiter"}
Type : @{Name=System.String}
CmdletSupported : Get-Content
PossibleValues :
Name : Delimiter
Description : Specifies the delimiter to use when reading the file. The default is "\n" (end of line).
Au passage, le guide utilisateur de la RC2 ne précise pas que les providers fournis en standard viennent avec une aide en ligne, accessible par « get-content providername ». Essayez avec tous les providers disponibles (« get-psprovider » pour avoir la liste complète), vous découvrirez sans doute plein d'autres choses !
Janel October 10 J'ai de la chance!Vous connaissez sans doute le moteur de recherche Google. Ce moteur implémente une fonction intitulée « J’ai de la chance » (« I’m feeling lucky » dans sa version originale). Le site de Google décrit cette fonction ainsi :
Le bouton « J'ai de la chance » affiche directement (et uniquement) la page Web considérée par Google comme la plus pertinente pour la requête exprimée. Dans ce cas, aucune autre page n'est citée. En utilisant le bouton « J'ai de la chance », vous passez moins de temps à rechercher les pages Web qui vous intéressent, ce qui vous laisse plus de temps pour les exploiter.
(source : http://www.google.fr/intl/fr/features.html)
Très intéressant, mais l’exercice peut se révéler amusant. Je me souviens d’un vieux gag avec cette fonction où on tapait un mot du genre « stupide » et on atterrissait sur la biographie de George W. Bush. Mais voici un nouveau gag presqu’aussi savoureux (en tout cas pour ceux qui s’intéressent à la guerre commerciale que se livrent les géants de l’Internet) :
D’abord, assurez-vous d’être sur la page US du site :
Ensuite, tapez « search » comme critère de recherche et cliquez sur « I’m Feeling Lucky ». Comme disent les américains : Enjoy !
Janel |
|
|