Functions vs Scripts and Profiles

Functions vs Scripts and Profiles

I've made the change.

Until now, I was declaring (here in the blog), prepping the grounds, and I've now set PowerShell as my default shell for my main Linux server, an Arch Linux that is driving my home media, NAS and other cloud services (including this blog).

I've already hit a few snags, although I've worked-around them, I don't consider them resolved just quite yet.

Problem: byobu

I use byobu as my tmux configuration. One thing byobu takes care of in any POSIX shell, is automatically loading itself when logged in.

It does not do that when PowerShell is the default shell.

So... I need to decide: do I write my own byobu loading code, or do I default the shell to a POSIX shell (I was using zsh) and have the profile load powershell for me.

At this point, although a bit slower to load - I opted to run byobu on my own in the profile.

Editing $profile.CurrentUserAllHosts I added the following code:

if ( -not ( Test-Path env:TMUX ) ) { byobu; exit }

For now it serves it's purpose.

Problem: Functions vs Scripts

This problem isn't completely unique to PowerShell nor to Linux, but after using zsh for a while, there is at least one aspect I'm going to need to change in my behavior.

Bash and Zsh have a neat little command named exec, this commands replaces the current process with the command passed to it to execute. This makes reloading the shell very easy:

exec $SHELL

With this handy command, editing the profile and restarting can be easily automated, and so I had a habit to stuff some semi-complex aliases and functions to the profile / dot-rc files the shell loads by default, and by that way curating my day-to-day automation.

This isn't good, for a few reasons, but in PowerShell - I don't think I have a choice here, and I'll need to abandon my habit.

Don't get me wrong, it's still possible to reload the profile, but it's not a complete reload of the shell, which is what I would have preferred.

The downside of this method (cramming the profile script with functions) is around efficiency, memory consumption and loading time.

The smart way would be to add a local script folder to $env:Path and utilizing scripts for my complex functions.

Why I didn't do it until now (in my POSIX shell usage)? I was lazy - editing everything in a single place, made it easier for me (at first, not anymore).I didn't need to remember where the files were, just edit a single file, also had an alias to quickly edit and reload shell, it was easy.

Why I should switch to it now? First, script files don't need to load on start. This will reduce profile loading time. Also, script files can be referenced from other shells - especially when starting with a bang (#!). Functions are relevant to the current session only, and so command like sudo which runs in a different user context, as well as a different permissions context, can run a script file by it's full path.

What I'll need here is a direcotry that will contain my personal modules and a directory that I will add to my $env:PATH that will contain my personal scripts.

My $Profile code

To do that, I've added the following code to my $profile.CurrentUserAllHosts:

$global:PathEnvDelimiter = $(if( $PSVersionTable.Platform -match 'unix' ) {':'} else {';'})
function Split-PathEnv {
param([string]$EnvPath)
  $EnvPath -split $PathEnvDelimiter
}

$global:MyPSModulePath = Split-PathEnv $env:PSModulePath | Where-Object { $_ -match "^$(Resolve-Path ~)" }

if( -not $MyPSModulePath ) {
  $MyPSModulePath = Resolve-Path ~/powershell/Modules
  if( -not (Test-Path $MyPSModulePath) ) {
      New-Item -ItemType Directory -Path $MyPSModulePath -Force | Out-Null
  }
  $env:PSModulePath = "$MyPSModulePath$PathEnvDelimiter$env:PSModulePath"
}

$local:p = Split-PathEnv $env:PATH
$global:MyPSScriptRoot = Join-Path (Split-Path -Parent $MyPSModulePath) Scripts
if( -not (Test-Path $MyPSScriptRoot) ) {
  New-Item -ItemType Directory -Path $MyPSScriptRoot -Force | Out-Null
}

$p = @($p[0], $MyPSScriptRoot) + $($p | Select-Object -Skip 1)
$env:PATH = $p -join $PathEnvDelimiter

At this point I'm free to fill up those two locations with my personal code.

Problem: Things I miss from Zsh

This is porbably going to be a recurring theme, which I started with my previous post.

I used wd extensivley in zsh, and after 'shopping around', I think the best replacement to this would be either fasdr or z (which also exist for zsh, which I didn't know of), I'll start with z, as it seems to be activley supported.

Install-Module z

What's next?

Adding the commands I already had written for myself in zsh, so they'll be avialable for me in PowerShell. These will either be commands loaded in Modules or scripts located on the path.

There is also the issue of sudo which needs to be addressed, making sure both PowerShell commands and scripts, as well as any other program on the Linux machine can be invoked by sudo without issues.