If you don't mind a sorted output, you could obtain a good result with a pipeline using a sed script plus sort like this:
$ sed -nE 's%/+(/[^/]|$)%\1%g;:loop;\%(^[^/]+|/[^/]*)$%{p;s///};tloop' | LC_ALL=C sort -u
The above Sed script goes to the extent of coalescing adjacent / characters (the very first s command up to the first ; does that) but apart from that it has no knowledge of relative paths etc.
Note that the script is POSIX compliant although on some systems you might need to type it in multiline, for instance like this:
$ sed -nE 's%/+(/[^/]|$)%\1%g
:loop
\%(^[^/]+|/[^/]*)$%{
p;s///
}
tloop
' | LC_ALL=C sort -u
Note also that it cannot handle newlines in filenames. However, on enhanced tools that support -z option you can just add it to both sed and sort to obtain nul-delimited I/O.
I also had fun in making a script capable of full canonicalization, including any arbitrary . and .. occurrences as well as unqualified paths like dir1/file1 prepended by a leading ./. However, it does not resolve symlinks as it does not access the filesystem at all.
I used Awk for its useful builtin machineries to split and rebuild strings.
#!/usr/bin/awk -f
BEGIN { FS="/+"; OFS="/" }
{
for (nfields=dotdot=0; NF; NF--) {
if (path[nfields]=$NF) {
if ($NF=="..") dotdot++
else if ($NF!=".") {
if (dotdot) dotdot--
else nfields++
}
}
}
if (nfields in path && !path[nfields])
$1=""
else {
while (dotdot--) path[nfields++]=".."
path[nfields++]="."
}
while (nfields--) {
$(NF+1) = path[nfields]
if (!seen[$0]++) print $0
}
}
You may handle newlines in filenames by invoking it with -v RS='\0' -v ORS='\0' on Awk implementations (such as GNU Awk) that support a nul RS.
HTH