views:

1347

answers:

10

Hello, I have little problem with specifying my variable. I have a file with normal text and somewhere in it there are brackets [ ] (only 1 pair of brackets in whole file), and some text between them. I need to capture the text within these brackets in a shell (bash) variable. How can I do that, please?

+4  A: 

Bash/sed:

VARIABLE=$(tr -d '\n' filename | sed -n -e '/\[[^]]/s/^[^[]*\[\([^]]*\)].*$/\1/p')

If that is unreadable, here's a bit of an explanation:

VARIABLE=`subexpression`      Assigns the variable VARIABLE to the output of the subexpression.

tr -d '\n' filename  Reads filename, deletes newline characters, and prints the result to sed's input

sed -n -e 'command'  Executes the sed command without printing any lines

/\[[^]]/             Execute the command only on lines which contain [some text]

s/                   Substitute
^[^[]*               Match any non-[ text
\[                   Match [
\([^]]*\)            Match any non-] text into group 1
]                    Match ]
.*$                  Match any text
/\1/                 Replaces the line with group 1
p                    Prints the line
strager
Pretty damn close to mine - you forgot to take the file name!
Jonathan Leffler
@Leffler, Oops, thanks! I had it in my test but left it out in the solution.
strager
OK - you've got a solution for [...] on a single line; I've got a solution for [ on one line and ] on another. How to combine so both work?
Jonathan Leffler
@Jonathan: In Perl it is trivial (it works for multiple non-overlapping brackets too): perl -0777 -ne'print $1 while /\[(.*?)\]/gs' infile.txt
J.F. Sebastian
Please consider using $() instead of ``. It is POSIX compatible and much easier to read and disambiguate.
camh
aww, perl is so sweet. i really should learn it some day -.-
Johannes Schaub - litb
Multiline mode added.
strager
+1  A: 

What about:

shell_variable=$(sed -ne '/\[/,/\]/{s/^.*\[//;s/\].*//;p;}' $file)

Worked for me on Solaris 10 under Korn shell; should work with Bash too. Replace '$(...)' with back-ticks in Bourne shell.

Edit: worked when given [ on one line and ] on another. For the single line case as well, use:

shell_variable=$(sed -n -e '/\[[^]]*$/,/\]/{s/^.*\[//;s/\].*//;p;}' \
                        -e '/\[.*\]/s/^.*\[\([^]]*\)\].*$/\1/p' $file)

The first '-e' deals with the multi-line spread; the second '-e' deals with the single-line case. The first '-e' says:

  • From the line containing an open bracket [ not followed by a close bracket ] on the same line
  • Until the line containing close bracket ],
  • substitute anything up to and including the open bracket with an empty string,
  • substitute anything from the close bracket onwards with an empty string, and
  • print the result

The second '-e' says:

  • For any line containing both open bracket and close bracket
  • Substitute the pattern consisting of 'characters up to and including open bracket', 'characters up to but excluding close bracket' (and remember this), 'stuff from close bracket onwards' with the remembered characters in the middle, and
  • print the result

For the multi-line case:

$ file=xxx
$ cat xxx
sdsajdlajsdl
asdajsdkjsaldjsal
sdasdsad [aaaa
bbbbbbb
cccc] asdjsalkdjsaldjlsaj
asdjsalkdjlksjdlaj
asdasjdlkjsaldja
$ shell_variable=$(sed -n -e '/\[[^]]*$/,/\]/{s/^.*\[//;s/\].*//;p;}' \
                          -e '/\[.*\]/s/^.*\[\([^]]*\)\].*$/\1/p' $file)
$ echo $shell_variable
aaaa bbbbbbb cccc
$

And for the single-line case:

$ cat xxx
sdsajdlajsdl
asdajsdkjsaldjsal
sdasdsad [aaaa bbbbbbb cccc] asdjsalkdjsaldjlsaj
asdjsalkdjlksjdlaj
asdasjdlkjsaldja
$
$ shell_variable=$(sed -n -e '/\[[^]]*$/,/\]/{s/^.*\[//;s/\].*//;p;}' \
                          -e '/\[.*\]/s/^.*\[\([^]]*\)\].*$/\1/p' $file)
$ echo $shell_variable
aaaa bbbbbbb cccc
$

Somewhere about here, it becomes simpler to do the whole job in Perl, slurping the file and editing the result string in two multi-line substitute operations.

Jonathan Leffler
+1  A: 

Assuming you are asking about bash variable:

$ export YOUR_VAR=$(perl -ne'print $1 if /\[(.*?)\]/' your_file.txt)

The above works if brackets are on the same line.

J.F. Sebastian
Once you get beyond a certain point, Perl becomes easier than sed.
Jonathan Leffler
A: 
var=`grep -e '\[.*\]' test.txt | sed -e 's/.*\[\(.*\)\].*/\1/' infile.txt`
John Ellinwood
A: 

Thanks to everyone, i used Strager's version and works perfectly, thanks alot once again...

var=`grep -e '\[.*\]' test.txt | sed -e 's/.*\[\(.*\)\].*/\1/' infile.txt`
Is it unreasonable to ask why it is not Strager's version that is selected as 'the answer' given this information?
Jonathan Leffler
A: 

Sed is not necessary:

var=`egrep -o '\[.*\]' FILENAME | tr -d ][`

But it's only works with single line matches.

Zsolt Botykai
+1  A: 

May I point out that while most of the suggested solutions might work, there is absolutely no reason why you should fork another shell, and spawn several processes to do such a simple task.

The shell provides you with all the tools you need:

$ var='foo[bar] pinch'
$ var=${var#*[}; var=${var%%]*}
$ echo "$var"
bar
lhunath
+1  A: 

Using Bash builtin regex matching seems like yet another way of doing it:

var='foo[bar] pinch'

[[ "$var" =~ [^][][([^[])].* ]] # Bash 3.0

var="${BASH_REMATCH[1]}"

echo "$var"

A: 

Backslashes (BSL) got munched up ... :

var='foo[bar] pinch' 
[[ "$var" =~ [^\]\[]*\[([^\[]*)\].* ]]   # Bash 3.0 
# Just in case ...: 
[[ "$var" =~ [^BSL]BSL[]*BSL[([^BSL[]*)BSL].* ]]   # Bash 3.0 
var="${BASH_REMATCH[1]}" 
echo "$var"
A: 

2 simple steps to extract the text.

  1. split var at [ and get the right part
  2. split var at ] and get the left part
cb0$ var='foo[bar] pinch'
cb0$ var=${var#*[}
cb0$ var=${var%]*} && echo $var
bar
cb0