Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
BASH: issue with grep and $[...]<.> in variables
View unanswered posts
View posts from last 24 hours

 
Reply to topic    Gentoo Forums Forum Index Portage & Programming
View previous topic :: View next topic  
Author Message
Dominique_71
Veteran
Veteran


Joined: 17 Aug 2005
Posts: 1895
Location: Switzerland (Romandie)

PostPosted: Sat Nov 09, 2024 11:00 am    Post subject: BASH: issue with grep and $[...]<.> in variables Reply with quote

I make a BASH script that scan a file with a list of files and create a temporary file with all the lines of theses files. These lines have 2 parts "key binding w/without modifiers command". That works fine.

From that, it create a preference file for later editing. As the original files can change over time, it have to compare each line of the temporary file with the preference file and copy into it only the new/changed lines according to an algorithm that begun to mostly works.

Extract of the temporary file:
Code:
key XF86AudioStop A $[Mod1] Music-Speed normal mplayer
key XF86AudioPlay A $[Mod1]2 Music-Speed normal mplayer
Key XF86AudioRaiseVolume A $[Mod0] Change-Volume-Up $[infostore.Vol_Step]
Key XF86AudioRaiseVolume A $[Mod0]2 Change-Volume-Up $[infostore.Vol_Step]


Extract of the script:
Code:
getcmd() {
cmds=$(echo $1 | grep -v -e "^#" -e "^$" | sed -e "s:   *   : :g" -e "s: * : :g" -e "s:.*Key:Key:" -e "s:'$::" | cut -d " " -f 5-)
#echo C: "${cmds}"
}
#getcmd

getkey() {
keys=$(echo $1 | grep -v -e "^#" -e "^$" | sed -e "s:   *   : :g" -e "s: * : :g" -e "s:.*Key:Key:" -e "s:'$::" | cut -d " " -f 1-4)
#echo K: "${keys}"
}

keyfuncexist() {
    grep -F -e "$1" -e "$2" "$3"
    # 1>/dev/null && echo found
}

write_bindingsfile() {
    while read myline ; do
          getcmd "${myline}"
          getkey "${myline}"
      # true  | true  == identical line exist; do nothing.
      #    if ! findidenticalline "${myline}" "${BindingsUserFile}"; then
             echo C:"${cmds}" F:"${keys}" L:"${myline}"
             keyfuncexist "${keys}" "${cmds}" "${BindingsUserFile}"
      #    fi
    done < "${BindingsTmpFile}"
}


keyfuncexist don't work as expected. As example, if
cmds="key XF86AudioStop A $[Mod1] Music-Speed normal mplayer", the grep -F will return both
Quote:
key XF86AudioStop A $[Mod1] Music-Speed normal mplayer
key XF86AudioPlay A $[Mod1]2 Music-Speed normal mplayer

when I want only the exact match, the first line. If I remove the -F, it is even worst because grep want to interpret the $[...]. So I have no clue about what to do here. Any help will be appreciated.
_________________
"Confirm You are a robot." - the singularity
Back to top
View user's profile Send private message
Goverp
Advocate
Advocate


Joined: 07 Mar 2007
Posts: 2179

PostPosted: Sat Nov 09, 2024 2:26 pm    Post subject: Reply with quote

Dominique_71,
grep -e foo -e bah
searchs for strings matching either foo or bah.

I note your file contents has key spelt sometimes with a capital K, others with lower case k. Is that intentional? Should you specify "-i" in your greps?

keyfuncexist with those parameters will match either lines containing either "key" or "XF86AudioStop" in file "A". You probably wanted
Code:
grep -F -e "$1 $2" "$3"
which matches the string "$1 $2", i.e. "key XF86AudioStop".
_________________
Greybeard
Back to top
View user's profile Send private message
Genone
Retired Dev
Retired Dev


Joined: 14 Mar 2003
Posts: 9612
Location: beyond the rim

PostPosted: Sat Nov 09, 2024 2:47 pm    Post subject: Reply with quote

I'd check if you can reimplement that in another language where you don't have to deal with 3+ different semantics for $ and other special characters that will sooner or later lead to problems with quoting/escaping.
Back to top
View user's profile Send private message
Dominique_71
Veteran
Veteran


Joined: 17 Aug 2005
Posts: 1895
Location: Switzerland (Romandie)

PostPosted: Sun Nov 10, 2024 12:02 am    Post subject: Reply with quote

Goverp wrote:
Dominique_71,
grep -e foo -e bah
searchs for strings matching either foo or bah.

Yep, I saw that, but didn't really pay attention to this and instead, put it on my todo list.

Quote:
I note your file contents has key spelt sometimes with a capital K, others with lower case k. Is that intentional? Should you specify "-i" in your greps?

Thanks for pointing that out. I will try it and check the original bindings file and make their case to be consistent, that even if fvwm don't care about the case.

Quote:
keyfuncexist with those parameters will match either lines containing either "key" or "XF86AudioStop" in file "A". You probably wanted
Code:
grep -F -e "$1 $2" "$3"
which matches the string "$1 $2", i.e. "key XF86AudioStop".

It is a check for identical line, it is commented into the extract. The script must follow that logic which simplify into the script, it is why I need to also test the bindings and the commands:
Code:
#  key  | func  | action
#-------|-------|--------
# true  | true  | line exist - do nothing
# true  | false | key exist - copy commented line
# false | true  | key don't exist - copy line
# false | false | line don't exist - copy line


I found a way to make grep to work with this:
Code:
keyexist() {
    tmpv=$(sed -e 's:\[:\\\[:g' -e 's:\]:\\\]:g' <<<"$1")
    grep "^$tmpv " "$2" 1>/dev/null
}

funcexist() {
    tmpv=$(sed -e 's:\[:\\\[:g' -e 's:\]:\\\]:g' <<<"$1")
    grep " $tmpv$" "$2" 1>/dev/null
}

I am almost done implementing the logic and will post the result later.
_________________
"Confirm You are a robot." - the singularity
Back to top
View user's profile Send private message
RumpletonBongworth
Tux's lil' helper
Tux's lil' helper


Joined: 17 Jun 2024
Posts: 82

PostPosted: Sun Nov 10, 2024 2:18 am    Post subject: Reply with quote

My advice is as follows.

Firstly, jettison all of the code that you have shown thus far.

Secondly, forget about sed and grep (for now).

Thirdly, consider that bash supports arrays. Not only that, but it supports associative arrays. While normal bash arrays map integers to strings, associative arrays map strings to strings, making them directly relevant to your problem domain.

The following code is for demonstrative purposes and may help to put you on the right track.

Code:
#!/bin/bash

# Synthesize some input data for testing.
mockdata='key XF86AudioStop A $[Mod1] Music-Speed normal mplayer
key XF86AudioPlay A $[Mod1]2 Music-Speed normal mplayer
Key XF86AudioRaiseVolume A $[Mod0] Change-Volume-Up $[infostore.Vol_Step]
Key XF86AudioRaiseVolume A $[Mod0]2 Change-Volume-Up $[infostore.Vol_Step]'

# Bash supports associative arrays. Use them!
declare -A cmd_by key_by

# Inputs may be treated as a series of fields.
while read -r firstcol key cmd; do
   if [[ $firstcol == [Kk]ey && $key && $cmd ]]; then
      cmd_by[$key]=$cmd # dictionary of commands by key name
      key_by[$cmd]=$key # dictionary of keys by command name
   fi
done <<<"$mockdata"

# Given an arbitrary key name, is it defined?
key='XF86AudioPlay'
if [[ ${cmd_by[$key]} ]]; then
   # Yes it is. Show the command defined for it.
   echo "$key => ${cmd_by[$key]}"
fi

# Given an arbitrary command definition, is it defined?
cmd='A $[Mod1] Music-Speed normal mplayer'
if [[ ${key_by[$cmd]} ]]; then
   # Yes it is. Show the key for which the command is defined.
   echo "$cmd => ${key_by[$cmd]}"
fi

# If you need to iterate over all the keys of an array, it is straightforward.
for key in "${!cmd_by[@]}"; do
   echo "$key => ${cmd_by[$key]}"
done
Back to top
View user's profile Send private message
Dominique_71
Veteran
Veteran


Joined: 17 Aug 2005
Posts: 1895
Location: Switzerland (Romandie)

PostPosted: Sun Nov 10, 2024 10:11 am    Post subject: Reply with quote

Genone wrote:
I'd check if you can reimplement that in another language where you don't have to deal with 3+ different semantics for $ and other special characters that will sooner or later lead to problems with quoting/escaping.


The freedesktop application menu with full support for the additional categories of the spec was added in FVWM-Crystal 3.0.5 back in 2011. Crystal was the first desktop to fully support that spec and for that, it contain a more than thousand lines bash script that scan the desktop files into /usr/share/applications/ and populate the application menu of that desktop.

From that time, most of the maintenance of that script was adding new sub categories into a case statement. That's not worst than with other languages like python or even C which deprecates/change/remove semantics and mnemonics. I think the only language that don't do that is assembler. With it, if You want other mnemonics, you change the hardware.

For me in fvwm-crystal, the issue is more with the different grep implementations than with the $ semantics. On linux, we have the gnu grep, when FreeBSD don't have it.
_________________
"Confirm You are a robot." - the singularity
Back to top
View user's profile Send private message
Dominique_71
Veteran
Veteran


Joined: 17 Aug 2005
Posts: 1895
Location: Switzerland (Romandie)

PostPosted: Sun Nov 10, 2024 12:29 pm    Post subject: Reply with quote

I got something that begin to works as expected:
Code:
#!/bin/bash
# 1st run: Generate preference files for the key or mouse bindings
#    from the file list into $FVWM_USERDIR/tmp/<Key|Mouse>BindingsList
#    and $FVWM_USERDIR/components/bindings if any.
#    Maybe TODO: When done, remove the files into $FVWM_USERDIR/components/bindings.
# Usage:
#   MakeBindingsFile <Key|Mouse>
# Mouse bindings are a wip.

usage() {
   echo "$(basename $0) usage:"
   echo "   $(basename $0) Key|Mouse"
   exit 1
}
if [ x"" == x"$1" ]; then
    usage
fi
if [ $1 != Key ]; then
    if [ $1 != Mouse ]; then
   usage
    fi
fi

# Preferences directoey and file
BindingsPrefDir="${FVWM_USERDIR}/preferences/bindings"
BindingsUserFile="${BindingsPrefDir}/${1}Bindings"
mkdir -p "${BindingsPrefDir}"
# path of temporary generated files
TmpDir="${FVWM_USERDIR}/tmp"
mkdir -p "${TmpDir}"
# default bindings files list; generated by crystal at Start and Restart
BindingsFilesList="${TmpDir}/${1}BindingsList"
# temporary bindings list
BindingsTmpFile="${TmpDir}/${1}Bindings"

# We must know if it is the first run
FRun="0"
if [ -f "${BindingsUserFile}" ]; then
    FRun="0"
else
    FRun="1"
fi

# make a bindings list from the files list
find_bindings() {
    echo -n '' > "${BindingsTmpFile}"

    while read myline; do
   echo "## From ${myline}: {{{1" >> "${BindingsTmpFile}"
   cat "${myline}" | sed -e "s:   *   : :g" -e "s: * : :g" >> "${BindingsTmpFile}"
    done < "${BindingsFilesList}"
}

# get the key and function strings associated to a binding
getkey() {
    keys=$(echo $1 | grep -i -v -e "^#" -e "^$" | sed -e "s:   *   : :g" -e "s: * : :g" -e "s:.*Key:Key:" -e "s:'$::" | cut -d " " -f 1-4)
}

getfunc() {
    funcs=$(echo $1 | grep -i -v -e "^#" -e "^$" | sed -e "s:   *   : :g" -e "s: * : :g" -e "s:.*Key:Key:" -e "s:'$::" | cut -d " " -f 5-)
}

identicalline() {
    grep -x -F "$1" "$2" 1>/dev/null || echo "$1"
}

findidenticalline() {
    grep -x -F "$1" "$2" 1>/dev/null
}

keyexist() {
    tmpv=$(sed -e 's:\[:\\\[:g' -e 's:\]:\\\]:g' <<<"$1")
    grep "^$tmpv " "$2" 1>/dev/null
}

funcexist() {
    tmpv=$(sed -e 's:\[:\\\[:g' -e 's:\]:\\\]:g' <<<"$1")
    grep " $tmpv$" "$2" 1>/dev/null
}

write_bindingsfile() {
#    echo -n "" > "${BindingsUserFile}"
    # We want only 1 vim line at the end of the file.
    sed -i 's/# vim:ft=fvwm/# New line additions:/' "${BindingsUserFile}"

    while read myline ; do
   # if non empty line
   if [[ -n "${myline}" ]]; then
       # skip wim lines
       if [[ "# vim:ft=fvwm" != "${myline}" ]]; then
      case "${myline}" in
      \#*)
          # copy commented lines as it only if they dont exist
          identicalline "${myline}" "${BindingsUserFile}" >> "${BindingsUserFile}"
          ;;
      *)
          getkey "${myline}"
          getfunc "${myline}"
          #  key  | func  | action
          #-------|-------|--------
          # true  | true  | line exist - do nothing
          # true  | false | key exist - copy commentd line
          # false | true  | key don't exist - copy line
          # false | false | line don't exist - copy line
          # TODO: after adding a line, info and ask for editing

          # true  | true  == identical line exist; do nothing.
          if ! findidenticalline "${myline}" "${BindingsUserFile}"; then
         echo C:"${funcs}" F:"${keys}" L:"${myline}"
             # ! identical && key exist => true false
         if keyexist "${keys}" "${BindingsUserFile}" ; then
             echo "#${myline}" >> "${BindingsUserFile}"
             echo "Add commented line with existing key binding and a different function:"
             echo "    ${myline}"
         else
             echo "${myline}" >> "${BindingsUserFile}"
             echo "Add new keybinding: ${myline}"
         fi
          fi
          ;;
      esac
       fi
   else
       # copy empty lines first time
       if [[ "${FRun}" == "1" ]]; then
      echo "${myline}" >> "${BindingsUserFile}"
       fi
   fi
    done < "${BindingsTmpFile}"
    echo "# vim:ft=fvwm" >> "${BindingsUserFile}"
}

# main()
if [ -f "${BindingsTmpFile}" ]; then
    write_bindingsfile "${1}"
else
    if [ -f "${BindingsFilesList}" ]; then
   find_bindings "${1}"
   write_bindingsfile "${1}"
    else
   echo "For $(basename $0) to run, restart FVWM-Crystal."
   rm -f "${BindingsTmpFile}"
   exit 2
    fi
fi

FvwmCommand "AT 'FVWM-Crystal Bindings Editor' ${EDITOR} ${BindingsUserFile}"


Edit: With the 'true false' condition, when an existing key combination bind to a different action, the existing one can be an user preference. It is why the script add the new one as a commented line. I never lost a single user preference with Crystal, and I want it to continue that way.
_________________
"Confirm You are a robot." - the singularity
Back to top
View user's profile Send private message
Dominique_71
Veteran
Veteran


Joined: 17 Aug 2005
Posts: 1895
Location: Switzerland (Romandie)

PostPosted: Mon Nov 11, 2024 9:31 am    Post subject: Reply with quote

Genone wrote:
I'd check if you can reimplement that in another language where you don't have to deal with 3+ different semantics for $ and other special characters that will sooner or later lead to problems with quoting/escaping.

But You are not necessarily wrong, because bash can be a hell with special characters and not only the $. As example with that script, if I try the sed '<blabla>' <<<"$var" in the console, it expand the [...] into the variable when the script don't do that.

That script allow me to waterproof the algorithm used to generate the user preference file. Making it to work with the Mouse bindings showed me another issue that I fixed. That issue is important because, even if its cause was into the system files, users can make similar things or worse. That show I must do a complete copy of these original system and user binding files, and use it as reference in all cases.

When I am done with that algorithm, I will consider what I will do with it and if I want to have a gui. fvwmscript can do that, but it is very time consuming to do it well with complicated things. And it don't scale with different font size preferences, which complicate further the scripts with no really good way to do that: The script and widget sizes must be calculated in some relatively arbitrary way and passed to the script at launch. Another option will be to add a supplementary dependency like PyGtk and do the whole thing in python.
_________________
"Confirm You are a robot." - the singularity


Last edited by Dominique_71 on Mon Nov 11, 2024 10:03 am; edited 3 times in total
Back to top
View user's profile Send private message
Dominique_71
Veteran
Veteran


Joined: 17 Aug 2005
Posts: 1895
Location: Switzerland (Romandie)

PostPosted: Mon Nov 11, 2024 9:44 am    Post subject: Reply with quote

RumpletonBongworth wrote:
My advice is as follows.

Thirdly, consider that bash supports arrays. Not only that, but it supports associative arrays. While normal bash arrays map integers to strings, associative arrays map strings to strings, making them directly relevant to your problem domain.

The following code is for demonstrative purposes and may help to put you on the right track.


Thanks for the advice and smart and concise explanation and code example. I will take a deep look at it later.
_________________
"Confirm You are a robot." - the singularity
Back to top
View user's profile Send private message
RumpletonBongworth
Tux's lil' helper
Tux's lil' helper


Joined: 17 Jun 2024
Posts: 82

PostPosted: Mon Nov 18, 2024 11:02 pm    Post subject: Reply with quote

Dominique_71 wrote:
But You are not necessarily wrong, because bash can be a hell with special characters and not only the $. As example with that script, if I try the sed '<blabla>' <<<"$var" in the console, it expand the [...] into the variable when the script don't do that.

This doesn't make sense. Not even history expansion in an interactive shell could interfere with the code (precisely as it is written there). Do you have a reproducible example that demonstrates a difference in behaviour between an interactive and a non-interactive shell?
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Portage & Programming All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum