PowerShell utilities series: ls / Get-ChildItem a color
No se puede entender el día a día de un administrador de sistemas sin scripting y línea de comandos, bien sea en bash desde UNIX y derivados o PowerShell para sistemas Windows; algo que ya nos es muy familiar en Plain Concepts. Con esta entrada inicio una serie de artículos dedicados a contar curiosidades y pequeñas utilidades de PowerShell, que tienen como finalidad hacer nuestra vida más fácil incrementando la experiencia de usuario con ella.
No es ningún secreto que muchos de los potenciales administradores de sistemas Windows vienen o tiene relación también con UNIX, por lo que Microsoft se ha preocupado de facilitar la transición de un sistema a otro. Para ello, PowerShell dispone de alias de muchas de sus funciones que permiten que los cmdlets se presenten en forma de formato UNIX, por ejemplo:
Alias utilizable en PowerShell | Cmdlet que en realidad ejecuta |
ls | Get-ChildItem |
cat | Get-Content |
grep | Select-String |
wc | Measure-Object |
time | Measure-Command |
find | Find-ChildItem |
man | Get-Help |
cp | Copy-Item |
mv | Move-Item |
rm | Remove-Item |
cls | Clear-Host |
echo | Write-Output |
Sin embargo, una de las cosas que más echo de menos de una consola UNIX es su capacidad de mostrar los archivos de un directorio por colores en función de su tipo, a través de la orden ls –color. Esto se puede ver en esta salida de ejemplo:
En cambio el resultado estándar de imprimir los contenidos de un directorio en PowerShell es el siguiente:
¿Verdad que sería estupendo si pudiéramos hacer que la PowerShell también tuviera su propio ls –color? Esto es justo lo que vamos a hacer ahora.
Consiguiendo el LS a color
Indagando por Internet di con un excelente script en GitHub firmado por un tal Tojo2000. Podéis ver y descargar el script desde aquí. El script daba buen resultado, pero había un par de cosas que echaba en falta frente al ls –color de UNIX:
- Los colores de los tipos de archivo no coincidían.
- Los enlaces simbólicos y enlaces duros del sistema de archivos NTFS no se mostraban en el clásico color celeste.
Lo bueno del scripting de PowerShell es que por definicion es Open Source, así que me lancé a hacer unas pequeñas modificaciones para que cumpliera con mis premisas. El resultado lo podéis ver a continuación:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
function Get-ChildItemColor { <# .Synopsis Returns childitems with colors by type. From http://poshcode.org/?show=878 .Description This function wraps Get-ChildItem and tries to output the results color-coded by type: Compressed - Red Directories - Blue Executables - Green Text Files - White Configs - Yellow Symlinks - Cyan Others - Default .ReturnValue All objects returned by Get-ChildItem are passed down the pipeline unmodified. .Notes NAME: Get-ChildItemColor AUTHOR: Tojo2000 <tojo2000@tojo2000.com> MODIFIED: Karloch <karloch@hispamsx.org> * Change colors to match UNIX style * Added background color for directories * Added symlink detection support #> function Test-ReparsePoint([string]$path) { $file = Get-Item $path -Force -ea 0 return [bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint) } $regex_opts = ([System.Text.RegularExpressions.RegexOptions]::IgnoreCase ` -bor [System.Text.RegularExpressions.RegexOptions]::Compiled) $fore = $Host.UI.RawUI.ForegroundColor $back = $Host.UI.RawUI.BackgroundColor $compressed = New-Object System.Text.RegularExpressions.Regex( '.(zip|tar|gz|rar|7z)$', $regex_opts) $executable = New-Object System.Text.RegularExpressions.Regex( '.(exe|com|bat|cmd|py|pl|ps1|psm1|vbs|rb|reg|fsx)$', $regex_opts) $dll_pdb = New-Object System.Text.RegularExpressions.Regex( '.(dll|pdb)$', $regex_opts) $configs = New-Object System.Text.RegularExpressions.Regex( '.(config|conf|ini|cfg)$', $regex_opts) $text_files = New-Object System.Text.RegularExpressions.Regex( '.(txt|cfg|conf|ini|csv|log)$', $regex_opts) $links = New-Object System.Text.RegularExpressions.Regex( '.(lnk)$', $regex_opts) Invoke-Expression ("Get-ChildItem $args") | %{ $c = $fore $b = $back $file = Get-Item $_.Name -Force -ea 0 if ([bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint)) { $c = 'Cyan' } elseif ($_.GetType().Name -eq 'DirectoryInfo') { $c = 'Blue' #$b = 'Gray' } elseif ($compressed.IsMatch($_.Name)) { $c = 'Red' } elseif ($executable.IsMatch($_.Name)) { $c = 'Green' } elseif ($text_files.IsMatch($_.Name)) { $c = 'White' } elseif ($dll_pdb.IsMatch($_.Name)) { $c = 'DarkGreen' } elseif ($configs.IsMatch($_.Name)) { $c = 'Yellow' } elseif ($links.IsMatch($_.Name)) { $c = 'Cyan' } $Host.UI.RawUI.ForegroundColor = $c $Host.UI.RawUI.BackgroundColor = $b echo $_ $Host.UI.RawUI.ForegroundColor = $fore $Host.UI.RawUI.BackgroundColor = $back } } |
El funcionamiento del script no entraña ningún misterio, aunque debo decir que es ingenioso. Básicamente utiliza expresiones regulares para identificar las extensiones de archivo de los elementos listados, para después llamar a Get-ChildItem cuya salida se procesa a través de un pipe donde se evalúa línea a línea de qué color debe pintarse en pantalla.
Básicamente podéis ver como se han cambiado los colores a la vez que se han agregado nuevas extensiones de archivo y además tenemos la siguiente diferencia:
1 2 3 |
if ([bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint)) { $c = 'Cyan' } |
Este if nos indicará si el archivo a listar es un ReparsePoint, que nos viene a decir si es un enlace en el sistema de archivos NTFS.
Una vez generado el script sólo tenemos que llamarlo para disponer de la orden Get-ChildItemColor. Sin embargo, eso no bastará para sustituir al ls y además, es engorro tener que estar cargando continuamente el archivo al cargar la consola. Por ello nos vamos a construir un sencillo instalador.
Los Profile de PowerShell
Un Profile de PowerShell no es más que un script que se ejecuta automáticamente cada vez que se arranca la línea de comandos. En función del ámbito de aplicación de dicho script (usuario actual, toda la máquina, etc…) este se puede ubicar en múltiples lugares. Por ejemplo: el $profile de All Users se ejecutará independientemente de quien sea el usuario que ha iniciado la consola de PowerShell.
Hay nada más y nada menos que 6 profile distintos de PowerShell:
Aplicación | Ruta |
Current User, Current Host – console | $Home[My ]DocumentsWindowsPowerShellProfile.ps1 |
Current User, All Hosts | $Home[My ]DocumentsProfile.ps1 |
All Users, Current Host – console | $PsHomeMicrosoft.PowerShell_profile.ps1 |
All Users, All Hosts | $PsHomeProfile.ps1 |
Current user, Current Host – ISE | $Home[My ]DocumentsWindowsPowerShellMicrosoft.P owerShellISE_profile.ps1 |
All users, Current Host – ISE | $PsHomeMicrosoft.PowerShellISE_profile.ps1 |
Nosotros vamos a trabajar con el perfil de Current User. Hay mucha más información sobre los profiles en este post del blog de Hey, Scripting Guy!
El instalador de scripts de PowerShell
Para que el uso de nuestro nuevo ls a color sea transparente, podemos hacernos un pequeño instalador en la propia PowerShell que nos lo instale en nuestro Profile personal y se autoejecute cada vez que abramos una sesión. El instalador consta de los siguientes archivos:
- Get-ChildItemColor.ps1. El archivo que contiene el script en cuestión.
- Get-ChildItemColor_profile.ps1. Contiene el pequeño script que agregaremos a nuestro perfil personal.
- Install-ChildItemColor.ps1. El script que se encarga de copiar los archivos e iniciar la instalación.
Veamos los dedicados a temas de instalación:
1 2 3 4 5 6 7 8 |
# BEGIN Get-ChildItemColor Write-Host -NoNewline "Loading Get-ChildItemColor module..." . "C:Users$env:usernameDocumentsWindowsPowerShellGet-ChildItemColor.ps1" # read the colourized ls set-alias ls Get-ChildItemColor -force -option allscope function Get-ChildItem-Force { ls -Force } set-alias la Get-ChildItem-Force -option allscope Write-Host -ForegroundColor Green "[OK]" # END Get-ChildItemColor |
Este script no tiene ningún misterio. En primer lugar hacemos dot sourcing del script en cuestión para que este cargue y después hacermos override de los alias por defecto de ls y la que están incluidos por defecto en PowerShell.
Vayamos al siguiente script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Write-Host -NoNewline "Welcome to the " Write-Host -ForegroundColor Green -NoNewLine "C" Write-Host -ForegroundColor Red -NoNewLine "O" Write-Host -ForegroundColor Yellow -NoNewLine "L" Write-Host -ForegroundColor Cyan -NoNewLine "O" Write-Host -ForegroundColor Magenta -NoNewLine "R" Write-Host " Get-ChildItem installer" Write-Host "Carlos Milán Figueredo <cmilan@plainconcepts.com>" Write-Host "" $PSPROFILE = "C:Users$env:usernameDocumentsWindowsPowerShell" $file = "Get-ChildItemColor.ps1" $file_profile = "Get-ChildItemColor_profile.ps1" if(!(Test-Path $PSPROFILE)) { Write-Host "'$PSPROFILE' not found, creating..." mkdir $PSPROFILE } Write-Host "Copying $file..." copy $file $PSPROFILE Write-Host "Appending aliases and functions to PowerShell user profile..." Get-Content $file_profile | Out-File -FilePath "$PSPROFILEMicrosoft.PowerShell_profile.ps1" -Append Write-Host "Installation finished" |
Este script:
- Identifica cuál debería ser la ruta donde se encuentra el profile de CurrentUser, y en caso de no encontrarla creará la carpeta por nosotros.
- Copia el archivo Get-ChildItemColor a la carpeta del profile.
- Agrega el contenido de Get-ChildItemColor_profile.ps1 a Microsoft.PowerShell_profile.ps1.
Con esto dejamos el script listo para ejecutarse automáticamente cada vez que abramos nuestra PowerShell y el resultado será el siguiente:
Os dejo agregados a este post un paquete con los 3 archivos para que lo podáis instalar con sólo ejecutar Install-Get-ChildItemColor.ps1.
Happy scripting!
Descarga: Get-ChildItemColor.zip
No sé mucho de Power shell, pero me parece que la sintaxis de commandos es mucho peor, o mas fea al menos que la de Unix.
Comandos básicos de DOS palabras. No hay alguna sintaxis mas Unix friendly?. O corta al menos.
Definitivamente el planteamiento de PowerShell es totalmente opuesto al de la mayoría de shells de UNIX, en especial el bash que es la que conozco. UNIX, al igual que C, vela procura que los comandos sean lo más cortos posible para que puedan ser escritos rápidamente e incrementar la productividad; pero la desventaja de esto es que con frecuencia son bastante crípticos y es complicado saber lo que hacen si no se consulta la ayuda del programa en cuestión.
PowerShell es opuesta y vela por que los comandos sigan una estructura predefinida (Verbo-Sustantivo) de forma que se comprenden fácilmente con sólo leerlos, sin haber estudiado el comando previamente. La desventaja de esto es que los comandos son muy largos y se tarda más en escribirlos, ¡aunque tenemos autocompletar!
A pesar de todo la PowerShell tiene definidos un set de alias por defecto para simular comandos de UNIX y MS-DOS, de forma que podemos usar en ella sin problemas el ls, grep, dir, cp, mv, etc… Así que la sintaxis UNIX-friendly la tienes de forma aproximada 🙂