7

I need to join multple Url elements into one string, so I wrote the generic Join-Parts function:

filter Skip-Null { $_|?{ $_ } }

function Join-Parts
{
    param
    (
        $Parts = $null,
        $Separator = ''
    )

    [String]$s = ''
    $Parts | Skip-Null | ForEach-Object {
        $v = $_.ToString()
        if ($s -ne '')
        {
            if (-not ($s.EndsWith($Separator)))
            {
                if (-not ($v.StartsWith($Separator)))
                {
                    $s += $Separator
                }
                $s += $v
            }
            elseif ($v.StartsWith($Separator))
            {
                $s += $v.SubString($Separator.Length)
            }
        }
        else
        {
            $s = $v
        }
    }
    $s
}

Join-Parts -Separator '/' -Parts 'http://mysite','sub/subsub','/one/two/three'
Join-Parts -Separator '/' -Parts 'http://mysite',$null,'one/two/three'
Join-Parts -Separator '/' -Parts 'http://mysite','','/one/two/three'
Join-Parts -Separator '/' -Parts 'http://mysite/','',$null,'/one/two/three'
Join-Parts 1,2,'',3,4

Which returns as expected:

http://mysite/sub/subsub/one/two/three
http://mysite/one/two/three
http://mysite/one/two/three
http://mysite/one/two/three
1234

I have the feeling that this is not the smartest approach. Any ideas on a better approach?

UPDATE

Based on the answer by @sorens I changed the function into:

function Join-Parts
{
    param
    (
        $Parts = $null,
        $Separator = ''
    )

    ($Parts | ? { $_ } | % { ([string]$_).trim($Separator) } | ? { $_ } ) -join $Separator 
}
Serge van den Oever
  • 4,340
  • 8
  • 45
  • 66

5 Answers5

8

Building on the answer from @mjolinor this one-liner passes all the tests in your question:

($parts | ? { $_ } | % { ([string]$_).trim('/') } | ? { $_ } ) -join '/' 

If you do not really care about the last test case (1,2,'',3,4) and can assume all inputs are strings you can shorten that to:

($parts | ? { $_ } | % { $_.trim('/') } | ? { $_ } ) -join '/' 

Note that I have two null/empty filters (? { $_ } ) present: the first strips nulls or empty strings from the input, which rectifies your test case with an empty string ('http:/fdfdfddf','','aa/bb'). The second is also necessary, catching input reduced to empty by the trim function.

If you really want to be fastidious about it, you should add one more trim to eliminate whitespace-only values since those are likely unwanted:

($parts | ? { $_ } | % { $_.trim('/').trim() } | ? { $_ } ) -join '/' 

With this last one these test case inputs will also return http://mysite/one/two:

$parts = 'http://mysite',''     ,'one/two' # empty
$parts = 'http://mysite','     ','one/two' # whitespace
$parts = 'http://mysite','    /','one/two' # trailing virgule
$parts = 'http://mysite','/    ','one/two' # leading virgule
$parts = 'http://mysite','/   /','one/two' # double virgule
Michael Sorens
  • 35,361
  • 26
  • 116
  • 172
  • @SergevandenOever, now you have me wondering:-) you had accepted my answer months ago and now you have switched to a different answer also from months ago. And yet, mjolinar's answer passes only two of the five test cases in your question, while mine passes all five. Did I miss something ? – Michael Sorens Aug 20 '12 at 23:17
  • you are absolutely right! I was checking questions I didn't give an answer to on a small screen, overlooked I already accepted your answer! I switched it back! Sorry:-) – Serge van den Oever Aug 21 '12 at 09:27
  • 1
    I downvoted this solution for one very important reason: READABILITY. This solution is very much unreadable to anyone without advanced PowerShell knowledge and thus should not be propagated. – Migol Mar 27 '20 at 09:08
  • Looks like in all cases the first `? { $_ }` is redundant. – roxton Dec 05 '20 at 16:52
  • @roxton First thank you for noticing that PowerShell has evolved so that the first null/empty filter is no longer needed!! While I appreciate your enthusiasm to also suggest an edit, I rejected it because it lost some context -- someone reading the original post that still showed the filter would be scratching their head a bit as to how he got from here to there. That's why I think your clear, concise comment just above (that it is redundant) is better. Thanks again! – Michael Sorens Dec 07 '20 at 04:20
7

Here's an example creating URLs using the UriBuilder class:

$builder = New-Object System.UriBuilder
$builder.Host = "www.myhost.com"
$builder.Path = ('folder', 'subfolder', 'page.aspx' -join '/')
$builder.Port = 8443
$builder.Scheme = 'https'
$builder.ToString()

Outputs:

https://www.myhost.com:8443/folder/subfolder/page.aspx

Update - here's a little function that should be able to combine your URL parts:

function Join-Parts {
    param ([string[]] $Parts, [string] $Seperator = '')
    $search = '(?<!:)' + [regex]::Escape($Seperator) + '+'  #Replace multiples except in front of a colon for URLs.
    $replace = $Seperator
    ($Parts | ? {$_ -and $_.Trim().Length}) -join $Seperator -replace $search, $replace
}

Join-Parts ('http://mysite','sub/subsub','/one/two/three') '/'
Join-Parts ('http://mysite',$null,'one/two/three') '/'
Join-Parts ('http://mysite','','/one/two/three') '/'
Join-Parts (1,2,'',3,4) ','

Outputs:

http://mysite/sub/subsub/one/two/three
http://mysite/one/two/three
http://mysite/one/two/three
1,2,3,4
Andy Arismendi
  • 50,577
  • 16
  • 107
  • 124
  • Didn't know about this class! Tried with System.Uri. Problem is still with Path for multiple parts. Need mjoliner his answer for that one! But thanks! – Serge van den Oever Mar 07 '12 at 00:22
  • @SergevandenOever Yea System.Uri is the immutable version. You can get the System.Uri object from the builder by accessing the Uri property `$builder.Uri`. – Andy Arismendi Mar 07 '12 at 00:25
  • @SergevandenOever I added a little function to my answer that should be able to combine your fragments. – Andy Arismendi Mar 07 '12 at 02:55
  • Hi Andy, thanks for the extra function. Looks a bit expensive though. The answer by @msorens looks much lighter, so I chose his answer. Thanks for all your great thinking on my problem! – Serge van den Oever Mar 08 '12 at 23:54
7

You could do something like this:

($parts | foreach {$_.trim('/'))} -join '/'
mjolinor
  • 66,130
  • 7
  • 114
  • 135
6

Drawing inspiration from the top answer at Path.Combine for URLs?

function Combine-UriParts ($base, $path)
{
    return [Uri]::new([Uri]::new($base), $path).ToString()
}

Should be easy enough to extend to multiple parts

Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198
2

Powershell has a -join operator. for help type help about_join

Richard
  • 106,783
  • 21
  • 203
  • 265
rerun
  • 25,014
  • 6
  • 48
  • 78