8

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 
}
1
  • Have you considered a URIBuilder ? Commented Mar 6, 2012 at 23:31

5 Answers 5

9

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
Sign up to request clarification or add additional context in comments.

5 Comments

@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 ?
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:-)
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.
Looks like in all cases the first ? { $_ } is redundant.
@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!
7

You could do something like this:

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

5 Comments

I could not edit, but perfect answer. Must be: ($parts | foreach {$_.trim('/')}) -join '/'
Thanks. I missed that brace.
But still not exactly: ('http:/fdfdfddf','','aa/bb' | ForEach-Object {$_.trim('/')}) -join '/' becomes http:/fdfdfddf//aa/bb, note the extra '/'
What does GIGO mean? :-) Ahhh... Garbage in, Garbage out... but that is exactly my problem:-) I have multiple parts comming from an xml configuration file.
I can understand having multiple parts. None of your sample data indicate they were going to be as malformed as that mess.
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

4 Comments

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!
@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.
@SergevandenOever I added a little function to my answer that should be able to combine your fragments.
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!
7

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

Comments

2

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.