Aquest document treballa l’scripting en dos mons: el lliure (Bash, sobre GNU/Linux) i el propietari (PowerShell, sobre Windows). L’objectiu no és només aprendre sintaxi, sinó saber quan i com un guió és l’eina adequada per administrar un sistema: comptes d’usuari, processos, serveis i tasques automatitzades.
| Bash (lliure) | PowerShell (propietari*) | |
|---|---|---|
| Model de dades | Text pla (streams) | Objectes .NET |
| Ordres | Binaris externs (ls, grep…) |
Cmdlets Verb-Noun |
| Extensió de fitxer | .sh |
.ps1 |
| Shebang | #!/bin/bash |
No aplica (s’invoca amb l’intèrpret) |
| Multiplataforma | Nativament Unix/Linux | PowerShell 7+ és multiplataforma |
* Des de PowerShell 7, Microsoft n’ha alliberat el codi (MIT license) i funciona sobre Linux i macOS, però el manual el tracta com a representant del món “propietari” perquè és l’eina d’administració per defecte de Windows Server i AD.
El fet que Bash treballi amb text i PowerShell amb objectes és la
diferència conceptual més important. A Bash, ps aux | grep bash filtra línies de text. A PowerShell, Get-Process | Where-Object {$_.Name -eq "pwsh"} filtra
objectes Process reals, amb propietats tipades
(.Id, .CPU, .Path…).
Bash — tot és text per defecte, sense declaració de tipus:
nom="Ramon"
edat=42
readonly VERSIO="1.0" # constant
declare -i comptador=0 # força el tipus enter
export PATH_LOG="/var/log/app.log" # variable d'entornPowerShell — tipat dinàmic amb objectes .NET reals:
$nom = "Ramon"
$edat = 42
[int]$comptador = 0
$PATH_LOG = "/var/log/app.log"
Set-Variable -Name VERSIO -Value "1.0" -Option ReadOnly# Bash
serveis=("ssh" "nginx" "mariadb")
echo "${serveis[1]}" # nginx
echo "${serveis[@]}" # tots els elements
declare -A usuaris=( [ramon]="professor" [alex]="alumne" )
echo "${usuaris[ramon]}" # professor# PowerShell
$serveis = @("ssh", "nginx", "mariadb")
$serveis[1] # nginx
$usuaris = @{ ramon = "professor"; alex = "alumne" }
$usuaris["ramon"] # professor# Condicional
if [ "$edat" -ge 18 ]; then
echo "major d'edat"
elif [ "$edat" -eq 0 ]; then
echo "no vàlid"
else
echo "menor d'edat"
fi
# Selecció múltiple
case "$1" in
start) echo "Arrencant...";;
stop) echo "Aturant...";;
*) echo "Ús: $0 {start|stop}";;
esac
# Bucles
for s in "${serveis[@]}"; do echo "$s"; done
i=0
while [ $i -lt 5 ]; do echo $i; ((i++)); done# Condicional
if ($edat -ge 18) {
"major d'edat"
} elseif ($edat -eq 0) {
"no vàlid"
} else {
"menor d'edat"
}
# Selecció múltiple
switch ($args[0]) {
"start" { "Arrencant..." }
"stop" { "Aturant..." }
default { "Ús: $($MyInvocation.MyCommand.Name) {start|stop}" }
}
# Bucles
foreach ($s in $serveis) { $s }
for ($i = 0; $i -lt 5; $i++) { $i }reinicia_servei() {
local servei="$1"
if systemctl is-active --quiet "$servei"; then
systemctl restart "$servei"
echo "Reiniciat: $servei"
else
echo "Error: $servei no actiu" >&2
return 1
fi
}
reinicia_servei "nginx"function Reinicia-Servei {
param(
[Parameter(Mandatory)][string]$Servei
)
if ((Get-Service -Name $Servei).Status -eq "Running") {
Restart-Service -Name $Servei
Write-Output "Reiniciat: $Servei"
} else {
Write-Error "$Servei no actiu"
}
}
Reinicia-Servei -Servei "Spooler"#!/bin/bash
#
# Nom: backup_logs.sh
# Autor: Ramon López
# Data: 2026-06-20
# Objectiu: Comprimeix i arxiva els logs de /var/log/app
# Ús: ./backup_logs.sh [directori_desti]
set -euo pipefail # atura el script davant errors, variables no definides o pipes fallitschmod +x backup_logs.sh
./backup_logs.sh /mnt/backupsset -euo pipefail és probablement la línia més important
de qualsevol script Bash professional:
-e: surt si qualsevol ordre retorna un error.-u: error si s’usa una variable no definida.-o pipefail: un pipe (cmd1 | cmd2) falla
si qualsevol de les dues ordres falla, no només
l’última.# Comentari de capçalera basat en comment-based help (veure secció 5.4)
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
.\Backup-Logs.ps1 -Desti "D:\Backups"Per defecte, Windows bloqueja l’execució d’scripts
(Restricted). Cal establir una política com RemoteSigned (permet scripts locals, exigeix signatura als
descarregats d’internet) — això mateix es tracta com a criteri
d’implantació al bloc 6.
| Eina | Ús |
|---|---|
bash -n script.sh |
Comprova la sintaxi sense executar |
bash -x script.sh |
Traça d’execució (mostra cada ordre abans d’executar-la) |
shellcheck script.sh |
Analitzador estàtic extern, detecta errors habituals i mals hàbits |
PSScriptAnalyzer |
Equivalent a ShellCheck per a PowerShell |
Set-PSDebug -Trace 1 |
Traça d’execució en PowerShell |
bash -x backup_logs.sh+ set -euo pipefail
+ DEST=/mnt/backups
+ tar czf /mnt/backups/app-2026-09-06.tar.gz /var/log/appSet-PSDebug -Trace 1
.\Backup-Logs.ps1
Set-PSDebug -Offtrap 'echo "Error a la línia $LINENO" >&2' ERR
ordre_que_pot_fallar
echo "Codi de sortida: $?"try {
Get-Service -Name "ServeiInexistent" -ErrorAction Stop
}
catch {
Write-Error "Error capturat: $($_.Exception.Message)"
}
finally {
Write-Output "Bloc finally sempre executat"
}Un tècnic de sistemes passa més temps llegint scripts d’altri que escrivint-ne de nous. Cal saber identificar patrons habituals als scripts del mateix sistema operatiu.
# /etc/systemd/system/monitor-disc.service
[Unit]
Description=Monitor d'espai en disc
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/monitor-disc.sh
User=root
[Install]
WantedBy=multi-user.targetInterpretació: ExecStart invoca un script extern; Type=oneshot indica que s’executa i acaba (típic de
scripts, no de dimonis persistents); WantedBy determina en
quin target d’arrencada s’habilita.
També cal saber llegir:
/etc/profile.d/*.sh: scripts que carreguen l’entorn de
tots els usuaris a l’inici de sessió./etc/cron.d/, /etc/cron.daily/: scripts
programats pel sistema.initramfs o /etc/init.d/ en
sistemes amb SysVinit encara vigent.# $PROFILE — s'executa a l'inici de cada sessió PowerShell
notepad $PROFILE# Consultar una tasca programada i el script que invoca
Get-ScheduledTask -TaskName "ActualitzaInventari" |
Select-Object TaskName, State
(Get-ScheduledTask -TaskName "ActualitzaInventari").ActionsInterpretar un script no és només llegir-ne el codi: cal identificar qui l’invoca (systemd, cron, Task Scheduler, un altre script), amb quins privilegis s’executa i quines dependències (altres binaris, variables d’entorn, fitxers de configuració) necessita per funcionar correctament.
Get-Module -ListAvailable ActiveDirectory
Import-Module ActiveDirectory
Get-ADUser -Filter {Enabled -eq $false} | Select-Object Name, LastLogonDateEls mòduls (Import-Module) són l’equivalent PowerShell
de les llibreries: paqueten cmdlets especialitzats (AD, DNS, DHCP,
Hyper-V…) que estenen l’intèrpret sense escriure’ls des de zero.
# /usr/local/lib/funcions_admin.sh
comprova_root() {
[ "$EUID" -eq 0 ] || { echo "Cal executar com a root" >&2; exit 1; }
}
log() {
echo "$(date '+%F %T') - $1" >> /var/log/scripts_admin.log
}#!/bin/bash
source /usr/local/lib/funcions_admin.sh # o bé: . /usr/local/lib/funcions_admin.sh
comprova_root
log "Script iniciat"source (o el seu àlies .) carrega les
funcions al context actual, sense obrir un subprocés nou: és el
mecanisme bàsic per consultar i reutilitzar llibreries de funcions en
Bash.
#!/bin/bash
# alta_usuari.sh — dona d'alta un usuari i el crea al grup indicat
set -euo pipefail
usuari="$1"; grup="${2:-alumnes}"
useradd -m -s /bin/bash -G "$grup" "$usuari"
passwd -e "$usuari" # força el canvi de contrasenya al primer login
echo "Usuari $usuari creat i afegit a $grup"# Alta-Usuari.ps1
param(
[Parameter(Mandatory)][string]$Usuari,
[string]$Grup = "Alumnes"
)
$pwd = ConvertTo-SecureString "CanviaAquestaContrasenya!" -AsPlainText -Force
New-LocalUser -Name $Usuari -Password $pwd -PasswordNeverExpires:$false
Add-LocalGroupMember -Group $Grup -Member $Usuari
Write-Output "Usuari $Usuari creat i afegit a $Grup"ps aux --sort=-%cpu | head -5 # 5 processos que més CPU consumeixen
pgrep -f "python3 servidor.py" # localitzar per nom
kill -SIGTERM "$(pgrep -f servidor.py)"Get-Process | Sort-Object CPU -Descending | Select-Object -First 5
Stop-Process -Name "notepad" -Force#!/bin/bash
# gestiona_servei.sh start|stop|status <nom_servei>
accio="$1"; servei="$2"
case "$accio" in
start) systemctl start "$servei" && echo "$servei iniciat";;
stop) systemctl stop "$servei" && echo "$servei aturat";;
status) systemctl status "$servei" --no-pager;;
*) echo "Ús: $0 {start|stop|status} <servei>"; exit 1;;
esac# Gestiona-Servei.ps1
param(
[ValidateSet("Start","Stop","Status")][string]$Accio,
[Parameter(Mandatory)][string]$Servei
)
switch ($Accio) {
"Start" { Start-Service -Name $Servei; "$Servei iniciat" }
"Stop" { Stop-Service -Name $Servei; "$Servei aturat" }
"Status" { Get-Service -Name $Servei }
}# crontab -e
# Cada dia a les 2:30, executa la còpia de seguretat
30 2 * * * /usr/local/bin/backup_logs.sh /mnt/backups >> /var/log/backup.log 2>&1# Alternativa moderna: systemd timer
# /etc/systemd/system/backup.timer
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
[Install]
WantedBy=timers.target# Programar la mateixa tasca amb Task Scheduler des de PowerShell
$accio = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "-File C:\Scripts\Backup-Logs.ps1"
$disparador = New-ScheduledTaskTrigger -Daily -At 2:30am
Register-ScheduledTask -TaskName "BackupLogs" -Action $accio -Trigger $disparadorBash: capçalera de comentaris (autor, data, objectiu, ús) com a l’apartat 2.1, més comentaris explicatius abans de blocs no evidents.
PowerShell té un sistema natiu de documentació integrat, el comment-based help:
<#
.SYNOPSIS
Gestiona l'estat d'un servei de Windows.
.DESCRIPTION
Inicia, atura o consulta l'estat d'un servei indicat pel nom.
.PARAMETER Servei
Nom del servei de Windows a gestionar.
.EXAMPLE
.\Gestiona-Servei.ps1 -Accio Start -Servei Spooler
#>
param(...)Amb aquesta capçalera, Get-Help .\Gestiona-Servei.ps1 -Full genera automàticament
l’ajuda de l’script, exactament igual que amb un cmdlet natiu.
| Aspecte | Linux / lliure | Windows / propietari |
|---|---|---|
| Permisos d’execució | chmod +x script.sh |
No cal per si mateix; depèn de la política d’execució |
| Control de qui pot executar | Permisos POSIX (usuari/grup/altres) + sudo |
Set-ExecutionPolicy, signatura digital de codi |
| Ubicació estàndard | /usr/local/bin, /usr/local/sbin |
Carpeta pròpia + variable PATH, o mòdul instal·lat a $env:PSModulePath |
| Distribució massiva | Paquet .deb/.rpm, Ansible, scp + cron |
GPO, PowerShell DSC, Intune, signatura amb Set-AuthenticodeSignature |
| Comprovar integritat | sha256sum |
Get-FileHash |
# Signar un script per poder-lo distribuir amb política AllSigned
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert
Set-AuthenticodeSignature -FilePath .\Backup-Logs.ps1 -Certificate $cert“Implantar” un guió no acaba en copiar-lo al servidor: inclou triar on viu (repositori, control de versions), com s’invoca (manualment, per cron/Task Scheduler, com a servei), amb quins privilegis i com es revisa si deixa de funcionar (logs, alertes). Aquest és exactament el punt on el scripting es converteix en administració de sistemes real.
man bash / help (Bash intern) — referència
completa del llenguatge.Get-Help about_* en PowerShell — p. ex. Get-Help about_Functions.PSScriptAnalyzer — analitzador estàtic per a
PowerShell.man systemd.service, man systemd.timer —
unitats de systemd.