6

How do I extract the folder named test from the following string?

TRUE|/home/linux/test|(null)|
5
  • 2
    I guess this will do: echo 'TRUE|/home/linux/test|(null)|' | cut -f 2 --delim=\| | xargs basename . Is this what you had in mind? There are probably other solutions using awk, etc.
    – Ray
    Mar 11 at 0:09
  • Thank you, that is correct! Mar 11 at 0:30
  • 1
    echo 'TRUE|/home/linux/test|(null)|' | sed 's#.*/##;s#|(.*$##'
    – mchid
    Mar 11 at 1:33
  • 2
    I am guessing you are using a pipe-delimited format. Many CSV readers have options to specify a pipe instead of a comma as the separator.
    – qwr
    Mar 12 at 3:39
  • Is the string stored in a shell variable or in a file or something else?
    – Ed Morton
    Mar 24 at 10:39

5 Answers 5

13

Bash has a very easy way to match elements of a path (as long as your path is defined as a variable) with parameter expansion.

Using the ${parameter##word} construct, you search from the beginning of the line, and deletes everything up until the last match. In the case of a path, this will remove everything but the last directory, like this:

path='/path/to/my/stuff'; echo ${path##*/}

Output:

stuff

In your case though, you'll still have the final part |(null)| to get rid off. In this particular case, you could do a double parameter expansion, like this:

path='TRUE|/home/linux/test|(null)|'; path=${path##*/}; echo ${path%%|*}

Output:

test

So in this case, you first match from the beginning to the last / (using ${path##*/}), and then match from the end to the last | (using ${path%%|*}).

In this way, you can perform a lot of string manipulation by using only Bash builtin functionality.

11

There are already two working solutions here, but I just wanted to also point out the following method, which maybe maps onto the imperative mindset more.

echo 'TRUE|/home/linux/test|(null)|' | awk -F '|' '{print $2}' | xargs basename

Conceptually, we 1) get the second element of the input, and 2) get the basename of that path.

awk -F '|' '{print $2}' splits on the | character, and gives you the second element, which would be /home/linux/test. Then, you want extract test from the given path, which is what the basename command does. The additional xargs is annoying from an aesthetic perspective, but is necessary because (as far as I am aware) the basename command can't read paths from stdin.

As user @Ray points out in their comment, cut would also work just as well as awk in this same solution.

1
  • 1
    That is awksome! From the OP's question, I did not consider variations in directory depth in my Answer as you and @ArturMeinild pointed out.
    – stumblebee
    Mar 11 at 23:51
8

If you would like to get "Extra credit" for your assignment, Then use awk

echo 'TRUE|/home/linux/test|(null)|' | awk -F '[|/]' '{print $5}'

The awk option -F defines the field separator(s). When enclosed in brackets [|/] defines both | and / as a field separator. So "test" would be the fifth argument '{print $5}'

7
  • can you perhaps also tell me whether I can change the argument (null) to active after the call echo 'TRUE|/home/linux/test|(null)|' | ? Mar 11 at 2:12
  • such as echo 'TRUE|/home/linux/test|(null)|' | sed s/null/active/ ?
    – stumblebee
    Mar 11 at 2:28
  • Yes, it works like this. TRUE|/home/linux/test|(active)| Can the brackets at (active) also be removed? Mar 11 at 2:46
  • 3
    While this is generally a good solution (and awk is very powerful), unfortunately here it only works if test is the fifth substring with the separators |/, and I think this is dangerous when working with directories. If the directory was /home/linux/leet/test, this would return leet instead, and not the "last" directory test. Mar 11 at 13:57
  • 1
    This would fail if any directory name in the path contained | (which is a valid character in a file/directory name in Unix), e.g. if one of the directory names was foo|bar: 'TRUE|/home/foo|bar/test|(null)|'. You should use awk -F '[|/]' '{print $(NF-2)}' instead to solve that and the problem of a longer directory path, 'TRUE|/home/linux/leet/test|(null)|'.
    – Ed Morton
    Mar 24 at 10:33
4

Oh okay, here is a sed answer

$ echo 'TRUE|/home/linux/test|(null)|' | sed -r 's#.*/([^|]+)\|.*#\1#'
test

Explanation:

  • -r use extended regex
  • s#...#..# find and replace using # as an alternative delimiter to /
  • .*/ any number of any characters before / including / (consumes all the /s leaving only whatever follows the last one)
  • ([^|]+) save some characters that are not | to use later
  • \|.* | and whatever follows it (| is a special character, hence escaped - not needed in [ ] character classes)
  • \1 the saved pattern
1
  • 2
    @ArturMeinild maybe the # is a bad choice because this one makes my eyes go funny as well XD
    – Zanna
    Mar 12 at 7:44
3

It's not really clear from your question if you want to get the third item of an absolute path or if you want to get the last item of an absolute path.

  • If you want to get the third item of an absolute path, you can also use the following command that makes use of tr and cut:

    echo 'TRUE|/home/linux/test|(null)|' | tr '|' '/' | cut -d '/' -f 5
    

    tr '|' '/' converts | to /, then cut -d '/' -f 5 uses / as delimiter (-d '/') and outputs the 5th element (-f 5).

  • If you want to get the last item of an absolute path using tr and cut in a way similar to the above, you can also make use of the rev command as follows:

    echo 'TRUE|/home/linux/test|(null)|' | tr '|' '/' | rev | cut -d '/' -f 3 | rev
    

    tr '|' '/' converts | to /, then the first rev reverses the string, cut -d '/' -f 3 uses / as delimiter (-d '/') and outputs the 3rd element (-f 3), and finally the second rev reverses the string again.


References

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .