11

I'm trying to get the first character of a variable, but I'm getting a Bad substitution error. Can anyone help me fix it?

code is:

while IFS=$'\n' read line
do
  if [ ! ${line:0:1} == "#"] # Error on this line
  then
    eval echo "$line"
    eval createSymlink $line
  fi
done < /some/file.txt

Am I doing something wrong or is there a better way of doing this?

-- EDIT --

As requested - here's some sample input which is stored in /some/file.txt

$MOZ_HOME/mobile/android/chrome/content/browser.js
$MOZ_HOME/mobile/android/locales/en-US/chrome/browser.properties
$MOZ_HOME/mobile/android/components/ContentPermissionPrompt.js
Martyn
  • 16,432
  • 24
  • 71
  • 104

4 Answers4

16

To get the first character of a variable you need to say:

v="hello"
$ echo "${v:0:1}"
h

However, your code has a syntax error:

[ ! ${line:0:1} == "#"]
#                     ^-- missing space

So this can do the trick:

$ a="123456"
$ [ ! "${a:0:1}" == "#" ] && echo "doesnt start with #"
doesnt start with #
$ a="#123456"
$ [ ! "${a:0:1}" == "#" ] && echo "doesnt start with #"
$ 

Also it can be done like this:

$ a="#123456"
$ [ "$(expr substr $a 1 1)" != "#" ] && echo "does not start with #"
$ 
$ a="123456"
$ [ "$(expr substr $a 1 1)" != "#" ] && echo "does not start with #"
does not start with #

Update

Based on your update, this works to me:

while IFS=$'\n' read line
do
  echo $line
  if [ ! "${line:0:1}" == "#" ] # Error on this line
  then
    eval echo "$line"
    eval createSymlink $line
  fi
done < file
fedorqui
  • 275,237
  • 103
  • 548
  • 598
  • 1
    Now I see doubleDown commented it. Will delete when he posts his answer! – fedorqui Jul 18 '13 at 12:51
  • 1
    I'll give you a +1 for sportsmanship too! FYI I tried this : a="123456" | [ ! "${a:0:1}" == "#" ] && echo "doesnt start with #" and got a BS error on that too. Could it be something to do with bash version? – Martyn Jul 18 '13 at 12:57
  • Oh I see, uhms. What about `[ "${a:0:1}" != "#" ]` ?, maybe the ` [ ! a == b ]` is not nice for your bash version. – fedorqui Jul 18 '13 at 13:00
  • Nice to read that, @Martyn! You can get more info about this in http://tldp.org/LDP/abs/html/refcards.html – fedorqui Jul 18 '13 at 13:04
  • And if @doubleDown wants to post an answer I keep my promise of deleting mine :D With +3 votes it would even make me earn a Disciplined badge! – fedorqui Jul 18 '13 at 13:04
  • I'm game if @doubleDown wants to play that way :) – Martyn Jul 18 '13 at 13:06
  • If the first character is `!` it does not work @fedorqui 'SO stop harming' – alper Apr 20 '20 at 10:57
4

Adding the missing space (as suggested in fedorqui's answer ;) ) works for me.

An alternative method/syntax

Here's what I would do in Bash if I want to check the first character of a string

if [[ $line != "#"* ]]

On the right hand side of ==, the quoted part is treated literally whereas * is a wildcard for any sequence of character.

For more information, see the last part of Conditional Constructs of Bash reference manual:

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 in Pattern Matching

Checking that you're using the right shell

If you are getting errors such as "Bad substitution error" and "[[: not found" (see comment) even though your syntax is fine (and works fine for others), it might indicate that you are using the wrong shell (i.e. not Bash).

So to make sure you are using Bash to run the script, either

  • make the script executable and use an appropriate shebang e.g. #!/bin/bash
  • or execute it via bash my_script

Also note that sh is not necessarily bash, sometimes it can be dash (e.g. in Ubuntu) or just plain ol' Bourne shell.

Community
  • 1
  • 1
doubleDown
  • 8,048
  • 1
  • 32
  • 48
2

Try this:

while IFS=$'\n' read line
do
  if ! [ "${line:0:1}" = "#" ]; then
    eval echo "$line"
    eval createSymlink $line
  fi
done < /some/file.txt

or you can use the following for your if syntax:

if [[ ! ${line:0:1} == "#" ]]; then
jaypal singh
  • 74,723
  • 23
  • 102
  • 147
0

TIMTOWTDI ^^

while IFS='' read -r line
do
  case "${line}" in
    "#"*)  echo "${line}"
    ;;
    *)     createSymlink ${line}
    ;;
  esac
done  < /some/file.txt

Note: I dropped the eval, which could be needed in some (rare!) cases (and are dangerous usually).

Note2: I added a "safer" IFS & read (-r, raw) but you can revert to your own if it is better suited. Note that it still reads line by line.

Note3: I took the habit of using always ${var} instead of $var ... works for me (easy to find out vars in complex text, and easy to see where they begin and end at all times) but not necessary here.

Note4: you can also change the test to : *"#"*) if some of the (comments?) lines can have spaces or tabs before the '#' (and none of the symlink lines does contain a '#')

Olivier Dulac
  • 3,695
  • 16
  • 31