Skip to main content

Serie A TIM Prediction | Juventus vs Lazio

Ads

Removing Duplicate PATH Entries: Reboot

In my first post on removing duplicate PATH entries I used an AWK one-liner. In the second post I used a Perl one-liner, or more accurately, I tried to dissect a Perl one-liner provided by reader Shaun. Shaun had asked that if I was willing to use AWK (not Bash), why not use Perl? It occurred to me that one might also ask: why not just use Bash? So, one more time into the void.

For those who made it through the second post, don't worry; this one should be mercifully short by comparison. And although I could make it a one-liner by using lots of semicolons, I won't do that either.
The approach remains pretty much the same as in the AWK and Perl versions of the code: split the path on colons, use an associative array to determine whether a path element has been seen before, and then join the non-duplicate path elements back together with colons.
Note that I incorrectly stated in my previous posts that empty path elements also could be eliminated in this process. In reality, empty path elements are the same as putting "." in your path; it means the current directory. This error on my part was pointed out in a comment to the second post. I guess I should have read the man page.
To split the PATH into its individual elements, I'll change bash's record separator to a colon and then assign the PATH variable to an array:
IFS=: ipaths=($PATH)
Note the assignment to IFS on the same line as the assignment to the array; if you haven't seen this before, it's standard bash syntax:
A simple command is a sequence of optional variable assignments followed by blank-separated words and redirections, and terminated by a control operator.
Now that I have the path elements in the paths variable, I crank up an associative array and test each element to see if it's in the array (again, remember that in bash, array elements that don't exist will evaluate to blank):
declare -A a    # Need to declare the array as associative
for p in "${ipaths[@]}"
do
    [[ -z "${a[$p]}" ]]  &&  a[$p]=1  &&  opaths+=":$p"
done
The loop steps through each path element in the ipaths array and creates the new path in a variable named opaths. The body of the loop tests to see if the current path element's array entry is blank ([[ -z"${a[$p]}" ]]), which means that the path has not been seen before. If it hasn't been seen before, it sets the path element's array entry to something non-blank (a[$p]=1) and then adds the path element, the variable containing the output path opaths+=":$p" (colons added here).
Unfortunately, that fails for blank path elements: blank associative array keys are not allowed in bash. To fix that, I'll use a separate variable to determine if a blank path has been seen before. Another option would be to change "::" to something like "*CURRENT_DIR*" before processing the path and then change it back afterward. The new loop looks like this:
declare -A a
for p in "${ipaths[@]}"
do
    if [[ -z "$p" ]]; then
        [[ -z "$currdir" ]]  &&  currdir=1  &&  opaths+=":"
    else
        [[ -z "${a[$p]}" ]]  &&  a[$p]=1  &&  opaths+=":$p"
    fi
done
Since all the output paths are preceded by a colon, even the first one, the final output path needs to have the first colon removed. I do this with a simple substring evaluation:
export PATH="${opaths:1}"
Short and sweet. It's not as short as the Perl version or the original AWK version, but I could just put the code above into a bash function and pretend this version is really short:
export PATH="$(remove_path_dupes)"
I can only hope that this is the last time I write about removing duplicates from the PATH variable, but I can't make any guarantees.
Edit: the following is an addendum to my original post.
A comment by a reader "pepa65" on the previous posts suggested another all-bash solution, and quite frankly, it's a much slicker solution than mine:
IPATH='/usr/bin:/usr/local/bin::/usr/bin:/some folder/j:'
OPATH=$(n= IFS=':'; for e in $IPATH; do [[ :$n == *:$e:* ]] || n+=$e:; done; echo "${n:0: -1}")
echo $IPATH
echo $OPATH
To make it a bit easier to see, I'll unroll the part inside the command substitution (the $(...) expression):
n= IFS=':'
for e in $IPATH
do
    [[ :$n == *:$e:* ]]  ||  n+=$e:
done
echo "${n:0: -1}"
Rather than using an associative array to see if a path element is in the output path, it simply uses a globcomparison to see if the path element is already found in the output path variable.
At first you may be wondering why this even works, because in the section on pathname expansion (aka glob), the bash man page states:
After word splitting, unless the -f option has been set, bash scans each word for the characters *, ?, and [. If one of these characters appears, then the word is regarded as a pattern, and replaced with an alphabetically sorted list of filenames matching the pattern...
And we certainly don't want the asterisks in the pattern to expand to a list of filenames. But that's not a concern, because in the section about [[ expression ]] evaluation, the man page states:
... Word splitting and pathname expansion are not performed on the words between the [[ and ]] ...
So pathname expansion does not happen. To get to why it actually works, continue reading the [[expression ]] section, and a bit further down you'll see:
When the == and != operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below under Pattern Matching, as if the extglob shell option were enabled. ...
Note that like the bash regular expression operator =~, when pattern matching is involved, these operators are not symmetric, so the following won't work:

Comments

Popular posts from this blog

Маnсhеstеr Unitеd​ 9 match never lost with new coach

Just in case some Man United fans forgot that this is football, and there are always two teams on the field. Even if we had lost today, respect the effort and tenacity from the boys. There's no mistake. Sometimes coach just want to try another lineup to know which is best for team. And this match is worth to learn It's fine that you are posting this highlists right after the matches, but they're seriously weak. Why? You focus on slowmotion after any good effort instead of showing whole goal actions. For example, there isn't situation from this match that gave United a penalty. Why? 😂 You did great job but it could be better ;) ALL GOALS HERE 1-Burnley 8:25 (Barnes) 2-Burnley 9:18 (Wood) 3-United 9:40 (Pogba) 4-United 9:55 (Lindelof) PLEASE LIKE FOR OTHERS

YOU WILL NOT BELIEVE THIS FIGHT! | 37 KILLS Duo vs Squad | PUBG Mobile

PlayerUnknown’s Battlegrounds PUBG), the hit game on PC (and Xbox One) that popularized the battle royale genre, has made its way to  Android  and  iOS  devices. For a game as complex as this one, it’s a surprise that it runs as well as it does on your phone. However, the difference in platform comes with changes to the gameplay as well. We’ve compiled a list of things you need to know about PUBG on mobile so that you can keep feasting on that chicken dinner. 1. Customise your controls By default, you move with a virtual control stick on the left, while using your right thumb to control your aim. You fire, aim down sights, crouch, go prone and access your backpack with virtual buttons on the right side of the screen. However, you can customize the layout of the virtual buttons by sliding them around and changing their size and transparency. You can even save multiple layout presets. Find the button layout you’re most comfortable with so that you can brin

Atletico Madrid 2-0 Juventus Video Highlight

The match had appeared to be heading for a goalless draw at the Wanda Metropolitano, before Atleti defenders Gimenez (78) and Godin (83) gave Diego Simeone's side a huge advantage heading into the second leg in Turin on March 12. That will come as a big relief to the home team, who had earlier seen substitute Alvaro Morata's 70th-minute header ruled out by the Video Assistant Referee for a push on Giorgio Chiellini. Video highlight: Cristiano Ronaldo, with only one goal in his last eight Champions League games, tested Jan Oblak with an early long-range free kick which the Atletico goalkeeper did well to tip over. It was Atleti, though, who thought they had the best chance to open the scoring before half-time when referee Felix Zwayer decided that Mattia De Sciglio had tripped Diego Costa just inside the box. However, after consultation with the VAR, the correct decision was made with the Juve right-back having clearly caught Costa just outside the area, with Woj