Los sistemas Windows actuales permiten usar la shell Bash gracias al Windows Subsystem for Linux (WSL). Si somos usuarios habituales de Linux y, por algún motivo, nos vemos forzados a usar Windows, WSL nos ofrece una manera sencilla de usar una interfaz de comandos y unas herramientas que conocemos bien y con las que, seguramente, nos sintamos mucho más cómodos que con las nativas de Windows.
Sin embargo, a la hora de administrar sistemas Windows, recurrir a las herramientas del ecosistema de Linux no suele ser la mejor opción porque presentan algunas limitaciones al usarlas fuera de su entorno original.
En este artículo hablaremos de las diferencias de base entre PowerShell y Bash y de como estas diferencias vienen condicionadas por las características del ecosistema para el que fueron diseñadas.
Antes de PowerShell
Hasta hace muy poco la interfaz de línea de comando por defecto de Windows era el Símbolo del sistema o Command Prompt.
Esta era la interfaz de línea de comandos original de Windows y tiene sus raíces en el antiguo sistema operativo MS-DOS. MS-DOS fue un sistema operativo basado en texto, que se desarrolló para los primeros PC y que fue popular en los años 80 y principios de los 90, antes de que las interfaces gráficas de usuario se volvieran comunes.
Interfaz de MS-DOS 6 ejecutando el comando dir . |
Uno de los componentes de MS-DOS —y otras versiones de DOS— era el programa COMMAND.COM
, que se ejecutaba al terminar el arranque del sistema y que implementaba la interfaz de línea de comandos. Esta interfaz y su lenguaje de scripting —conocido como archivos batch (.BAT)— estaban muy limitados, en comparación con las shells de Unix/Linux —como BASH—, principalmente por las limitaciones del propio sistema y del hardware sobre el que iba a ejecutarse.
Cuando Microsoft introdujo Windows, inicialmente se ejecutaba como una interfaz gráfica de usuario sobre MS-DOS. Con el tiempo, Windows evolucionó para convertirse en un sistema operativo independiente e introdujo una nueva interfaz de línea de comandos identificada como el Símbolo del sistema. Esta interfaz era implementada por el programa cmd.exe
, pero sus características era muy similares a la de la antigua interfaz de MS-DOS.
Icono del Command Prompt de Windows NT 3.1 con el logo de MS-DOS. |
Además, cmd.exe
está estrechamente vinculado a la arquitectura del sistema operativo Windows y no se puede portar fácilmente a otros sistemas operativos. Esto significa que los scripts y comandos que funcionan en cmd.exe
a menudo no funcionan en otros sistemas operativos sin modificaciones significativas.
Símbolo del sistema en Windows 10. |
PowerShell fue introducido por Microsoft en 2006 para abordar muchas de estas limitaciones. PowerShell es un shell y lenguaje de scripting mucho más potente que cmd.exe
, con soporte para scripting basado en objetos y un conjunto de características que es comparable al de las shells Unix/Linux. PowerShell también ha sido portado a otros sistemas operativos, incluyendo Linux y macOS.
Aunque cmd.exe
sigue estando disponible en las versiones modernas de Windows —y sigue siendo posible ejecutar archivos .BAT— PowerShell se ha convertido en la shell por defecto del sistema y suele ser el preferido para muchas tareas relacionadas con la administración de sistemas.
¿Por qué PowerShell?
Desde las primeras versiones de Windows, Microsoft siempre ha tenido predilección por el desarrollo de interfaces gráficas de usuario (GUI). Esto no solo se aprecia en la evolución de Windows, sino también en el desarrollo de muchas de las aplicaciones y tecnologías asociadas, como es el caso de la mayoría de los asistentes y herramientas de configuración y administración del sistema.
Sin embargo, con el tiempo, se dieron cuenta de que las GUI, a pesar de su accesibilidad y facilidad de uso para la mayoría de los usuarios, presenta importantes limitaciones cuando se trata de administrar sistemas y de automatizar tareas, especialmente a gran escala. PowerShell fue la respuesta a esta necesidad.
La intención original era incluir en Windows un conjunto de herramientas similares a las de Unix1. Sin embargo, esto no funcionaba también como cabía espera. El motivo es que el ecosistema de Unix está construido en torno a los archivos de texto. En Unix/Linux la configuración e información del núcleo de sistema, servicios y aplicaciones está disponible en forma de archivos de texto, por lo que las múltiples herramientas de procesamiento de texto que acompañan a Bash son, efectivamente, herramientas de gestión.
Sin embargo, Windows es un ecosistema construido en torno a API y a objetos binarios —como las interfaces COM, WMI, el Directorio Activo, el Registro del sistema, etc.— por lo que las herramientas de procesamiento de texto tradicionales de Unix no pueden ayudar demasiado en la gestión del sistema. Es debido a esto desarrollaron PowerShell, con el objetivo de obtener una herramienta comparable a las shells de Unix/Linux pero en el ecosistema de Windows.
Secuencias de caracteres vs. Objetos
En Bash y otras shells de Unix/Linux, la mayoría de las operaciones se realizan sobre secuencias de caracteres. La salida de un comando, por medio de su salida estándar, es una secuencia de caracteres, que se puede redirigir a un archivo o a otro comando utilizando los operadores '>' y '|', respectivamente. Igualmente, los comandos pueden aceptar una secuencia de caracteres a través de su entrada estándar, que puede provenir de un archivo o de la salida de otro comando utilizando los operadores '<' y '|', respectivamente. Y las variables que se utilizan en los scripts, almacenan y se sustituyen como secuencias de caracteres.
Por ejemplo, el siguiente comando puede usarse para listar y filtrar procesos:
ps aux | grep python
El comando ps aux
genera un listado detallado de todos los procesos en ejecución, con una línea de información para cada proceso. La salida del comando devuelve dicha información como una secuencia de caracteres. El comando grep python
filtra este texto para mostrar solo las líneas que contienen la palabra "python". Si esta palabra aparece en el nombre del comando, es probable que se trate de una ejecución del intérprete del lenguaje Python.
El comando grep python
no busca la palabra en una propiedad específica del proceso, sino en toda la línea que contiene su información. Si, por ejemplo, solo interesa listar los procesos del usuario “python” o los procesos del comando “python”, haría falta buscar otra solución.
En contraste, PowerShell maneja la conexión entre comandos de manera diferente. En lugar de pasar secuencias de caracteres, PowerShell puede pasar objetos completos —de hecho, son objetos de .NET— dónde cada uno es una entidad con propiedades y métodos. Esto permite que los comandos que se encadenan en una tubería de PowerShell mediante el operador ‘|’, sean más conscientes de la estructura y el contenido de la información que reciben.
Por ejemplo, considera el comando equivalente en PowerShell del comando de Bash mencionado anteriormente:
Get-Process | Where-Object { $_.ProcessName -like "*python*" }
El comando Get-Process
genera una lista de objetos de proceso, cada uno de los cuales tiene propiedades como ProcessName
, Id
, etc. Mientras que el comando Where-Object
itera sobre los objetos evaluando la condición indicada, para filtrarlos basándose en sus propiedades; en lugar de buscar el texto "python" en una secuencia de caracteres sin estructura, como hace grep
. Concretamente, en el ejemplo anterior, Where-Object
extrae el valor de la propiedad ProcessName
del objeto actual y comprueba si esta contiene la subcadena "python". Es decir, que se pueden comprobar fácilmente condiciones en cualquiera de las propiedades de cada proceso del listado devuelto por Get-Process
.
Aunque lo pueda parecer, el resultado de Where-Object
no es texto, sino una lista de objetos que cumplen la condición indicada. Como la salida del Where-Object
no va a ser guardada en una variable ni a usarla con otro comando, el intérprete de PowerShell la convierte en texto para mostrársela al usuario en la consola.
Obviamente, PowerShell puede procesar textos como lo hace Bash. Tiene herramientas propias, que admiten y devuelven secuencias de caracteres, además de poder invocar las herramientas típicas de los sistemas Unix, si están disponibles en el sistema.
Tanto en Unix/Linux como en Windows, cualquier proceso puede tener una entrada y una salida estándar, siendo el mecanismo por el que las shells —incluidas Bash y PowerShell— pueden conectar la salida de texto de un comando con la entrada de otro. La diferencia es que, en PowerShell, adicionalmente, las funciones y los cmdlets permiten implementar comandos que reciben y devuelven objetos, en lugar de solo secuencias de caracteres.
En PowerShell ambos mundos se pueden unir fácilmente. Por ejemplo, supongamos que tenemos un listado de libros en un archivo de texto libros.json
en formato JSON.
{
"books": [
{"title": "1984", "author": "George Orwell", "year": 1949},
{"title": "Un mundo feliz", "author": "Aldous Huxley", "year": 1932},
{"title": "Fahrenheit 451", "author": "Ray Bradbury", "year": 1953}
]
}
Filtrar libros según algún criterio y seleccionar varias propiedades de este listado, usando solamente herramientas de procesamiento de texto, puede ser muy complejo. Sin embargo, podemos convertir el contenido del archivo en listas de objetos, para iterar fácilmente sobre ellos, filtrar y seleccionar la información que queremos mostrar al usuario.
(Get-Content libros.json | ConvertFrom-Json).books |
Where-Object { $_.year -lt 1950 } | ForEach-Object { $_.title }
Cmdlets en PowerShell
Los cmdlets —pronunciado como command-let— son un tipo de comando de PowerShell que se implementan mediante clases de .NET —heredando de la clase System.Management.Automation.Cmdlet
—. Generalmente, se usa un lenguaje como C# y se compilan en archivos .DLL, que PowerShell carga para ejecutar el comando dentro del contexto del intérprete o el script actual. Por tanto, son más ligeros que otros tipos de comandos porque no crean un proceso adicional cada vez que hay que ejecutarlos.
Los cmdlets son sencillos de identificar porque se nombran siguiendo la convención Verbo-Sustantivo. Por ejemplo, el comando Get-ChildItem
usa el verbo Get
seguido por el nombre ChildItem
. Cuando se ejecuta, lista el contenido de una o varias rutas, de forma similar a como funciona el comando ls
en Bash.
Get-ChildItem -Path C:\Users
Esta convención puede hacer que los comandos sean más largos en comparación a sus equivalentes en Bash, lo que puede no ser demasiado cómodo al trabajar con PowerShell de forma interactiva. Sin embargo, el objetivo de hacerlo así es que sean sencillos de recordar y de hacer los scripts más legibles y autoexplicativos, lo que puede facilitar la compresión y el mantenimiento del código.
Los cmdlets suelen ser pequeños, en el sentido de que realizan una única función o tarea específica, en línea con la filosofía de los comandos en Unix “hacer una cosa y hacerla bien”. Como hemos comentado, cada cmdlet puede aceptar objetos como entrada y producirlos como salida, lo que permite encadenar varios cmdlets juntos para realizar operaciones complejas en un solo comando.
Como los cmdlets se implementan en el framework .NET, tienen un acceso muy sencillo a las API del sistema, así como a las de servicios y aplicaciones instalados. Por tanto, resulta fácil crear cmdlets que hagan de interfaz de línea de comandos de esas API, con el objeto de facilitar la creación de scripts con los que automatizar todo tipo de tareas.
Por ejemplo, los módulos de PowerShell AzureAD
y MSOnline
ofrecen un conjunto de cmdlets con los que se pueden automatizar tareas o gestionar desde la shell tanto Azure Active Directory como Office 3652.
Volviendo al comando Get-ChildItem
, gracias al uso de las distintas API del sistema, no solo sirve para listar el contenido de directorios, como ls
en Bash, sino que también permite listar el contenido del almacén de certificados o del registro del sistema —donde Windows guardar su configuración—:
Get-ChildItem -Path HKLM:\SOFTWARE
Como lenguajes de scripting
Los sistemas Unix surgieron en una época anterior al predominio de las interfaces gráficas de usuario. En ese contexto, las shells como Bash fueron diseñadas para ser la interfaz principal de los usuarios, por lo que, necesitan proporcionar un conjunto de herramientas y comandos muy generales para resolver una gran cantidad de tareas diferentes. Asimismo, la eficiencia en la escritura y ejecución de comandos es fundamental, por lo que con frecuencia se usan nombres cortos para comandos y opciones.
Por el contrario, en Windows se espera que la mayoría de las interacciones de los usuarios con el sistema se realicen a través de la interfaz gráfica, en lugar de una línea de comandos. PowerShell no se diseñó para ser una interfaz alternativa a la interfaz gráfica, sino como una herramienta para facilitar la automatización de tareas y la administración de sistemas a los usuarios más avanzados. Por eso, PowerShell trae una serie de cmdlets que facilitan la administración y el mantenimiento de sistemas Windows y estos siguen una convención de nombres que facilita la legibilidad y el mantenimiento de los scripts. Sin embargo, esto da como resultado una sintaxis más verbosa, al menos para quiénes están acostumbrados a utilizar Bash y otras shells similares.
Otra diferencia importante entre ambas shell aparece en la forma en la que se procesan los comandos.
Bash es un intérprete de línea de comandos tradicional, que se puede decir que funciona de manera lineal. Es decir, cuando se introduce un comando, realiza sobre la línea múltiples expansiones en un orden específico —expansión de variables, sustitución de comandos, división de palabras o expansión de nombres de archivos— que van sustituyendo distintas partes del comando.
En este proceso, no se tienen en cuenta ningún tipo de contexto. Por ejemplo, si se usan comodines —como en la expresión A*.txt
— la expansión ocurrirá tanto si se ha puesto en el nombre del comando como si es en alguno de los argumentos. Si fuera este último caso, la shell no tiene en cuenta si en ese argumento se espera un nombre de archivo u otra cosa. La expansión ocurrirá siempre, con tal que exista algún archivo que encaje en la expresión.
Al final, tras todas las expansiones, lo que obtiene Bash es una lista de palabras de la que extrae el nombre del comando y sus argumentos para ejecutarlo.
El resultado es que, cuando se utiliza Bash como lenguaje de scripts, resulta ser un poco diferente a otros lenguajes de programación. Esto no significa que Bash no se use como lenguaje para desarrollar scripts para la administración del sistema, porque obviamente todo sistema Unix/Linux tiene muchos scripts en Bash. Pero cuando los scripts adquieren algo de complejidad, muchos administradores y desarrolladores prefieren utilizar otros lenguajes más convencionales, como Python.
PowerShell, por otro lado, tiene un enfoque más similar a la de un lenguaje de programación tradicional que a la de una shell de línea de comandos estilo Unix. En lugar de interpretar de forma lineal, sustituyendo sucesivamente distintas partes, el lenguaje PowerShell tiene una gramática bien definida. Como en cualquier lenguaje moderno, el intérprete toqueniza, analiza, construye el árbol de sintaxis abstracta (AST) del comando y finalmente evalúa el AST para ejecutar el comando. Es durante la evaluación de los elementos del comando, cuando se realizan todas las expansiones necesarias, como la expansión de variables, la sustitución de comandos o la expansión de nombres de archivos. Para hacer estas expansiones, PowerShell tiene en cuenta el contexto.
Por ejemplo, en Bash el siguiente comando imprime todos los archivos que empiezan por A y terminan por ‘.txt’ en el directorio actual:
echo A*.txt
El comando echo
sirve para imprimir una cadena por la salida estándar, pero Bash hará la expansión de nombres de archivo y luego llamará a echo
con todos esos archivos como argumentos del comando.
Sin embargo, en PowerShell ese mismo comando solo imprime A*txt
porque echo
—un alias de Write-Output
— espera como argumento una cadena de caracteres, no un nombre de archivo. Por tanto, no hay ningún motivo para que PowerShell aplique la expansión de nombres de archivo, transformando la cadena.
Ambos lenguajes de scripts ofrecen construcciones comunes en cualquier lenguaje de programación, como bucles o condicionales. Sin embargo, el enfoque basado en objetos de PowerShell favorece el uso de tuberías para resolver problemas mediante la combinación de cmdlets, antes que usar dichas construcciones.
Comandos y equivalencias
La siguiente tabla contiene una lista de los comandos más utilizados en Bash, junto con sus equivalentes en PowerShell y sus alias en esta shell.
En Bash | En PowerShell | Alias en PowerShell | Descripción |
ls | Get-ChildItem | gci , dir , ls | Listar los elementos en un directorio. |
cd | Set-Location | cd , chdir , sl | Cambiar el directorio actual. |
pwd | Get-Location | pwd , gl | Mostrar la ruta del directorio actual. |
man | Get-Help | man , help | Mostrar la ayuda de un comando. |
cp | Copy-Item | cp , copy , cpi | Copiar un elemento de un lugar a otro. |
mv | Move-Item | mv , move , mi | Mover un elemento de un lugar a otro. |
rm | Remove-Item | rm , rmdir , del , erase , ri | Eliminar uno o más elementos. |
cat | Get-Content | cat , type , gc | Mostrar el contenido de un archivo. |
echo | Write-Output | echo , write | Escribir la salida a la consola. |
find | Get-ChildItem -Recurse | Where-Object | gci -r | ? | Buscar archivos en un directorio. |
grep | Select-String | sls | Filtrar contenidos de un archivo según un patrón. |
ps | Get-Process | gps , ps | Mostrar información sobre los procesos en ejecución. |
kill | Stop-Process | spps , kill | Detener un proceso en ejecución. |
Como se puede observar, muchos de los comandos de PowerShell en el listado tienen un alias con el nombre del comando equivalente en Bash. Sin embargo, es importante tener en cuenta que, aunque estos comandos son equivalentes en términos de funcionalidad, pueden comportarse de manera ligeramente diferente o requerir diferentes parámetros dependiendo del comando y del sistema operativo.
"Is PowerShell ready to replace my Cygwin shell on Windows?". Stack Overflow. Accedido el 9 de junio de 2023. stackoverflow.com/questions/573623/is-power.. ↩
"Manage Microsoft 365 with Microsoft 365 PowerShell". Microsoft Learn. Accedido el 9 de junio de 2023. learn.microsoft.com/en-us/microsoft-365/ent.. ↩