I have a file 1.txt with content:
Package1
Package2
Package3
Package4
And another file 2.txt
Version
VersionExtend
Version
VersionExtend
I just want to concatenate these strings with a delimiter, which will be like below:
Package1:Version
Package2:VersionExtend
Package3:Version
Package4:VersionExtend
I tried
Get-Content 1.txt, 2.txt | Set-Content joinedFile.txt
But got this result:
Package1
Package2
Package3
Package4
Version
VersionExtend
Version
VersionExtend
Can you give me ideas on how to do it?
CodePudding user response:
Bring each file as a list of lines then use one of the approaches in this thread.
How can I "zip" two arrays in PowerShell?
CodePudding user response:
You're simply writing the content of the two files one after the other instead of processing corresponding lines.
PowerShell has no built-in way for enumerating collections by positionally corresponding elements,[1] but you can use .NET APIs:
# IMPORTANT:
# Since .NET's working dir. usually differs from PowerShell's, it
# is important to always use FULL PATHS when calling .NET methods.
# The calls to Convert-Path ensure that.
# Get enumerators for the lines of both files.
$fileA = [System.IO.File]::ReadLines((Convert-Path 1.txt))
$fileB = [System.IO.File]::ReadLines((Convert-Path 2.txt))
# Iterate over the files' lines in tandem, and write each pair
# to the output file.
& {
while ($fileA.MoveNext(), $fileB.MoveNext() -contains $true) {
'{0}:{1}' -f $fileA.Current, $fileB.Current
}
} | Set-Content joinedFile.txt
This solution is memory-friendly, as it reads the lines lazily, i.e., on demand, using the
System.IO.File.ReadLines
method, obviating the need to read the files into memory as a whole.If one of the files "runs out lines", an empty string is used in lieu of a value.
If you want to limit processing to only as many lines as the two files have in common, replace the
while
condition with:while ($fileA.MoveNext(), $fileB.MoveNext() -notcontains $false)
Alternatively, use a LINQ-based solution, as shown next.
LINQ-based solutions:
Important: The solutions below use the System.Linq.Enumerable.Zip
method, which assumes that the the input collections have the same number of elements. If they differ in element count, processing is invariably limited to as many elements as the smaller collection has, i.e. to how many elements the have in common.
- Memory-friendly too:
# Get enumerators for the lines of both files.
$fileA = [System.IO.File]::ReadLines((Convert-Path 1.txt))
$fileB = [System.IO.File]::ReadLines((Convert-Path 2.txt))
[Linq.Enumerable]::Zip(
$fileA,
$fileB,
[Func[string, string, string]] { '{0}:{1}' -f $args[0], $args[1] }
) | Set-Content joinedFile.txt
- Simpler alternative that reads both files in full first:
[Linq.Enumerable]::Zip(
(Get-Content -ReadCount 0 1.txt),
(Get-Content -ReadCount 0 2.txt),
[Func[object, object, string]] { '{0}:{1}' -f $args[0], $args[1] }
) | Set-Content joinedFile.txt
[1] A proposal to introduce such a feature, in the form of foreach ($elementFromA, $elementFromB in $collectionA, $collectionB) { ... }
, was rejected, unfortunately - see GitHub issue #14732.