3

I'm constructing a bash script file a bit at a time. I'm learning as I go. But I can't find anything online to help me at this point: I need to extract a substring from a large string, and the two methods I found using ${} (curly brackets) just won't work.

The first, ${x#y}, doesn't do what it should.

The second, ${x:p} or ${x:p:n}, keeps reporting bad substitution.
It only seems to work with constants.

The ${#x} returns a string length as text, not as a number, meaning it does not work with either ${x:p} or ${x:p:n}.

Fact is, it's seems really hard to get bash to do much math at all. Except for the for statements. But that is just counting. And this isn't a task for a for loop.

I've consolidated my script file here as a means of helping you all understand what it is that I am doing. It's for working with PureBasic source files, but you only have to change the grep's "--include=" argument, and it can search other types of text files instead.

#!/bin/bash
home=$(echo ~)                              # Copy the user's path to a variable named home  
len=${#home}                                # Showing how to find the length.  Problem is, this is treated  
                                            # as a string, not a number.  Can't find a way to make over into  
                                            # into a number.  
echo $home "has length of" $len "characters."  
read -p "Find what: " what                  # Intended to search PureBasic (*.pb?) source files for text matches  
grep -rHn $what $home --include="*.pb*" --exclude-dir=".cache" --exclude-dir=".gvfs" > 1.tmp  
while read line                             # this checks for and reads the next line  
do                                          # the closing 'done' has the file to be read appended with "<"  
  a0=$line                                  # this is each line as read  
  a1=$(echo "$a0" | awk -F: '{print $1}')   # this gets the full path before the first ':'  
  echo $a0                                  # Shows full line  
  echo $a1                                  # Shows just full path  
  q1=${line#a1}  
  echo $q1                                  # FAILED!  No reported problem, but failed to extract $a1 from $line.  
  q1=${a0#a1}  
  echo $q1                                  # FAILED!  No reported problem, but failed to extract $a1 from $a0.  
  break                                     # Can't do a 'read -n 1', as it just reads 1 char from the next line.  
                                            # Can't do a pause, because it doesn't exist.  So just run from the  
                                            # terminal so that after break we can see what's on the screen  .
  len=${#a1}                                # Can get the length of $a1, but only as a string  
  # q1=${line:len}                          # Right command, wrong variable   
  # q1=${line:$len}                         # Right command, right variable, but wrong variable type  
  # q1=${line:14}                           # Constants work, but all $home's aren't 14 characters long  
done < 1.tmp  
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
oldefoxx
  • 147
  • 4
  • If the comments in the latter half of your post are directed at Stack Overflow, then I apologize for your frustration; I can edit your post to be properly formatted if you'd like. First-time posting is harder than it should be for many users, because we do have very peculiar rules for post formatting and content requirements. I'd recommend removing the aforementioned portion of the question, as it may attract more users who are willing to answer. – Shotgun Ninja Sep 11 '15 at 13:51
  • Update: I've edited your post and cleaned up the code formatting. If I could do anything more to get your question answered quickly and reverse your frustration with SO, I would. – Shotgun Ninja Sep 11 '15 at 13:56
  • 1
    Your string/number type comment isn't valid. I don't know what you are seeing to confuse you but using `len=${#a1}; echo "${line:$len}"` works fine. You can omit the leading `$` on variables in arithmetic contexts (which the right-hand side of `:` in the substring expansions is) but that only works if the result is a number (so `${line#a1}` won't work). – Etan Reisner Sep 11 '15 at 14:34
  • Also be careful with `~` as it doesn't expand in strings/etc. Using `$HOME` might be a better idea (unless you need the `~user` format expansion in which case [this answer](http://stackoverflow.com/a/29310360/258523) (among others) might be of use. – Etan Reisner Sep 11 '15 at 14:38

2 Answers2

3

The following works:

x="/home/user/rest/of/path"
y="~${x#/home/user}"
echo $y

Will output

~/rest/of/path

If you want to use "/home/user" inside a variable, say prefix, you need to use $ after the #, i.e., ${x#$prefix}, which I think is your issue.

toth
  • 2,519
  • 1
  • 15
  • 23
0

The hejp I got was most appreciated. I got it done, and here it is:

#!/bin/bash
len=${#HOME}                                # Showing how to find the length.  Problem is, this is treated
                                            # as a string, not a number.  Can't find a way to make over into
                                            # into a number.
echo $HOME "has length of" $len "characters."
while :
do
  echo
  read -p "Find what: " what                # Intended to search PureBasic (*.pb?) source files for text matches
  a0=""; > 0.tmp; > 1.tmp
  grep -rHn $what $home --include="*.pb*" --exclude-dir=".cache" --exclude-dir=".gvfs" >> 0.tmp
  while read line                           # this checks for and reads the next line
  do                                        # the closing 'done' has the file to be read appended with "<"
    a1=$(echo $line | awk -F: '{print $1}') # this gets the full path before the first ':'
    a2=${line#$a1":"}                       # renove path and first colon from rest of line
    if [[ $a0 != $a1 ]]
    then
      echo  >> 1.tmp
      echo $a1":" >> 1.tmp
      a0=$a1
    fi
    echo " "$a2 >> 1.tmp
  done < 0.tmp
  cat 1.tmp | less
done

What I don't have yet is an answer as to whether variables can be used in place of constants in the dollar-sign, curly brackets where you use colons to mark that you want a substring of that string returned, if it requires constants, then the only choice might be to generate a child scriot using the variables, which would appear to be constants in the child, execute there, then return the results in an environmental variable or temporary file. I did stuff like that with MSDOS a lot. Limitation here is that you have to then make the produced file executable as well using "chmod +x filename". Or call it using "/bin/bash filename".

Another bash limitation found it that you cannot use "sudo" in the script without discontinuing execution of the present script. I guess a way around that is use sudo to call /bin/bash to call a child script that you produced. I assume then that if the child completes, you return to the parent script where you stopped at. Unless you did "sudo -i", "sudo -su", or some other variation where you become super user. Then you likely need to do an "exit" to drop the super user overlay.

If you exit the child script still as super user, would typing "exit" but you back to completing the parent script? I suspect so, which makes for some interesting senarios.

Another question: If doing a "while read line", what can you do in bash to check for a keyboard key press? The "read" option is already taken while in this loop.

oldefoxx
  • 147
  • 4