I want to include command-parameters-inline comments, e.g.:
sed -i.bak -r \
# comment 1
-e 'sed_commands' \
# comment 2
-e 'sed_commands' \
# comment 3
-e 'sed_commands' \
/path/to/file
The above code doesn't work. Is there a different way for embedding comments in the parameters line?
If you really want comment arguments, can try this:
ls $(
echo '-l' #for the long list
echo '-F' #show file types too
echo '-t' #sort by time
)
This will be equivalent to:
ls -l -F -t
echo is an shell built-in, so does not execute external commands, so it is fast enough. But, it is crazy anyway.
or
makeargs() { while read line; do echo ${line//#*/}; done }
ls $(makeargs <<EOF
-l # CDEWDWEls
-F #Dwfwef
EOF
)
I'd recommend using longer text blocks for your sed script, i.e.
sed -i.bak '
# comment 1
sed_commands
# comment 2
sed_commands
# comment 3
sed_commands
' /path/to/file
Unfortunately, embedded comments in sed script blocks are not universally a supported feature. The sun4 version would let you put a comment on the first line, but no where else. AIX sed either doesnt allow any comments, or uses a different char besides # for comments. Your results may vary.
I Hope this helps.
You could invoke sed multiple times instead of passing all of the arguments to one process:
sed sed_commands | # comment 1
sed sed_commands | # comment 2
sed sed_commands | # comment 3
sed sed_commands # final comment
It's obviously more wasteful, but you may decide that three extra sed processes are a fair tradeoff for readability and portability (to #shellter's point about support for comments within sed commands). Depends on your situation.
UPDATE: you'll also have to adjust if you originally intended to edit the files in place, as your -i argument implies. This approach would require a pipeline.
There isn't a way to do what you seek to do in shell plus sed. I put the comments before the sed script, like this:
# This is a remarkably straight-forward SED script
# -- When it encounters an end of here-document followed by
# the start of the next here document, it deletes both lines.
# This cuts down vastly on the number of processes which are run.
# -- It also does a substitution for XXXX, because the script which
# put the XXXX in place was quite hard enough without having to
# worry about whether things were escaped enough times or not.
cat >$tmp.3 <<EOF
/^!\$/N
/^!\\ncat <<'!'\$/d
s%version XXXX%version $SOURCEDIR/%
EOF
# This is another entertaining SED script.
# It takes the output from the shell script generated by running the
# first script through the second script and into the shell, and
# converts it back into an NMD file.
# -- It initialises the hold space with --#, which is a marker.
# -- For lines which start with the marker, it adds the pattern space
# to the hold space and exchanges the hold and pattern space. It
# then replaces a version number followed by a newline, the marker
# and a version number by the just the new version number, but
# replaces a version number followed by a newline and just the
# marker by just the version number. This replaces the old version
# number with the new one (when there is a new version number).
# The line is printed and deleted.
# -- Note that this code allows for an optional single word after the
# version number. At the moment, the only valid value is 'binary' which
# indicates that the file should not be version stamped by mknmd.
# -- On any line which does not start with the marker, the line is
# copied into the hold space, and if the original hold space
# started with the marker, the line is deleted. Otherwise, of
# course, it is printed.
cat >$tmp.2 <<'EOF'
1{
x
s/^/--#/
x
}
/^--# /{
H
x
s/\([ ]\)[0-9.][0-9.]*\n--# \([0-9.]\)/\1\2/
s/\([ ]\)[0-9.][0-9.]*\([ ][ ]*[^ ]*\)\n--# \([0-9.][0-9.]*\)/\1\3\2/
s/\([ ][0-9.][0-9.]*\)\n--# $/\1/
s/\([ ][0-9.][0-9.]*[ ][ ]*[^ ]*\)\n--# $/\1/
p
d
}
/^--#/!{
x
/^--#/d
}
EOF
There's another sed script in the file that is about 40 lines long (marked as 'entertaining'), though about half those lines are simply embedded shell script added to the output. I haven't changed the shell script containing this stuff in 13 years because (a) it works and (b) the sed scripts scare me witless. (The NMD format contains a file name and a version number separated by space and occasionally a tag word 'binary' instead of a version number, plus comment lines and blank lines.)
You don't have to understand what the script does - but commenting before the script is the best way I've found for documenting sed scripts.
No.
If you put the \ before the # it will escape the comment character and you won't have a comment anymore.
If you put the \ after the # it will be part of the comment and you won't escape the newline anymore.
A lack of inline comments is a limitation of bash that you would do better to adapt to than try and work around with some of the baroque suggestions already put forth.
Although the thread is quite old i did find it for the same question and so will others. Here's my solution for this problem:
You need comments, so that if you look at your code at a much later time you will likely get an idea of what you actually did, when you wrote the code. I am just having the same problem while writing my first rsync script, which has lots of parameters which also have side effects.
Group together your parameter which belong together by topic and put them into a variable, which gets a corresponding name. This makes it easy to identify what the parameter steer. This is your short comment. In addition you can put a comment above the variable declaration to see how you can change the behavior. This is the long version comment.
Call the application with the corresponding parameter variables.
## Options
# Remove --whole-file for delta transfer
sync_filesystem=" --one-file-system \
--recursive \
--relative \
--whole-file \ " ;
rsync \
${sync_filesystem} \
${way_more_to_come} \
"${SOURCE}" \
"${DESTIN}" \
Good overview, easy to edit and like comments in parameters. It takes more effort, but has therefore a higher quality.
I'll suggest another way that works at least in some instances:
Let's say I have the command:
foo --option1 --option2=blah --option3 option3val /tmp/bar`
I can write it this way:
options=(
--option1
--option2=blah
--option3 option3val
)
foo ${options[#]} /tmp/bar
Now let's say I want to temporarily remove the second option. I can just comment it out:
options=(
--option1
# --option2=blah
--option3 option3val
)
Note that this technique may not work when you need extensive escaping or quoting. I have run into some issues with that in the past, but unfortunately I don't recall the details at the moment :(
For most cases, though, this technique works well. If you need embedded blanks in a parameter, just enclose the string in quotes, as normal.
Related
I want to make a few configuration files (for homeassistant) that are very similar to each other. I am aiming to use a template file as the base and put in a few substitution strings at the top of the file and use a bash script to read the substitutions and run sed with the applicable strings.
i.e.
# substitutions
# room = living_room
# switch = hallway_motion
# delay = 3
automations:
foo......
.........
entity_id: $switch
When I run the script it will look for any line beginning with a # that has a word (key) and then an = and another word (maybe string) (value) and replace anywhere that key with a $ in front is in the rest of the file.
Like what is done by esphome. https://esphome.io/guides/configuration-types.html#substitutions
I am getting stuck at finding the "keys" in the file. How can I script this so it can find all the "keys" recursively?
Or is there something that does this, or something similar, out there already?
You can do this with sed in two stages. The first stage will generate a second stage sed script to fill in your template. I'd make a small adjustment to your syntax and recommend that you require curly braces around your variable name. In other words, write your variable expansions like this:
# foo = bar
myentry: ${foo}
This makes it easier to avoid pitfalls when you have one variable name that's a prefix of another (e.g., foo and foobar).
#!/bin/bash
in="$1"
stage2=$(mktemp)
trap 'rm -f "$stage2"' EXIT
sed -n -e 's,^# \([[:alnum:]_]\+\) = \([^#]\+\),s#\${\1}#\2#g,p' "$in" > "$stage2"
sed -f "$stage2" "$in"
Provide a filename as the first argument, and it will print the filled out template on stdout.
This example code is pretty strict about white space on variable definition lines, but that can obviously be adjusted to your liking.
I have some text files $f resembling the following
function
%blah
%blah
%blah
code here
I want to append the following text before the first empty line:
%
%This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike
%3.0 Unported License. See notes at the end of this file for more information.
I tried the following:
top=$(cat ./PATH/text.txt)
top="${top//$'\n'/\\n}"
sed -i.bak 's#^$#'"$top"'\\n#' $f
where the second line (I think) preserves the new line in the text and the third line (I think) substitutes the first empty line with the text plus a new empty line.
Two problems:
1- My code appends the following text:
%n%This work is licensed under the Creative Commons
Attribution-NonCommercial-ShareAlike n%3.0 Unported License. See notes
at the end of this file for more information.\n
2- It appends it at end of the file.
Can someone please help me understand the problems with my code?
If you are using GNU sed, following would work.
Use ^$ to find the empty line and then use sed to replace/put the text that you want.
# Define your replacement text in a variable
a="%\n%This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike\n%3.0 Unported License. See notes at the end of this file for more information."
Note, $a should include those \n that will be directly interpreted by sed as newlines.
$ sed "0,/^$/s//$a/" inputfile.txt
In the above syntax, 0 represents the first occurrence.
Output:
function
%blah
%blah
%
%This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike
%3.0 Unported License. See notes at the end of this file for more information.
%blah
code here
test
You've included bash and sed tags in your question. Since I can't seem to come up with a way of doing this in sed, here's a bash-only solution. It's likely to perform the worst of all working solutions you might find.
The following works with your sample input:
$ while read -r x; do [[ -z "$x" ]] && cat boilerplate; printf '%s\n' "$x"; done < src
This will however insert the boilerplate before EVERY blank line, which is probably not what you're after. Instead, we should probably make this more than a one-liner:
#!/usr/bin/env bash
y=true
while read -r x; do
if [[ -z "$x" ]] && $y; then
cat boilerplate
y=false
fi
printf '%s\n' "$x"
done < src
Note that unlike the code in your question, this doesn't store your boilerplate in a variable, it just cats it "at the right time".
Note that this sends the combined output to stdout. If your goal is to modify the original file, you'll need to wrap this in something that moves around temporary files. (Note that sed's -i option also doesn't really edit files in place, it only hides the moving-around-temp-files from you.)
The following alternatives are probably a better idea.
A similar solution to the bash one might be achieved with better performance using awk:
awk 'NR==FNR{b=b $0 ORS;next} /^$/&&!y{printf "%s",b;y++} 1' boilerplate src
This awk solution obviously reads your boilerplate into a variable, though it's not a shell variable.
Notwithstanding non-standard platform-specific extensions, awk does not have any facility for editing files "in place" either. A portable solution using awk would still need to push temp files around.
And of course, the following old standard of ed is great to keep in your back pocket:
printf 'H\n/^$/\n-\n.r boilerplate\nw\nq\n' | ed src
In bash, of course, you could always use heretext, which might be clearer:
$ ed src <<< $'H\n/^$/\n-\n.r boilerplate\nw\nq\n'
The ed command is non-stream version of sed. Or rather, sed is the stream version of ed, which has been around since before the dinosaurs and is still going strong.
The commands we're using are separated by newlines and fed to ed's standard input. You can discard stdout if you feel the urge. The commands shown here are:
H - instruct ed to print more useful errors, if it gets any.
/^$/ - search for the first occurrence of a newline.
- - GO BACK ONE LINE. Awesome, right?
.r boilerplate - Read your boilerplate at the current line,
w - and write the file.
q - Quit.
Note that this does not keep a .bak file. You'll need to do that yourself if you really want one.
And if, as you suggested in comments, the filename you're reading is to be constructed from a variable, note that variable expansion does not happen inside format quoting ($' .. '). You can either switch quoting mechanisms mid-script:
ed "$file" <<< $'H\n/^$/\n-\n.r ./TATTOO_'"$currn"$'/top.txt\nw\nq\n'
Or you could put ed script in a variable constructed by printf
printf -v scr 'H\n/^$/\n-\n.r ./TATTOO_%s/top.txt\nw\nq\n' "$currn"
ed "$file" <<< "$scr"`
Adding the text to a variable so you can interpolate the variable is wasteful and an unnecessary complication. sed can easily read the contents of a file by itself.
sed -i.bak '1r./PATH/text.txt' "$f"
Unfortunately, this part of sed is poorly standardized, so you may have to experiment a little bit. Some dialects require a newline (perhaps, or perhaps not, preceded by a backslash) before the filename.
sed -i.bak '1r\
./PATH/text.txt' "$f"
(Notice also the double quotes around the file name. You generally always want double quotes around variables which contain file names. More here.)
Adapting the recipe from here we can extend this to apply to the first empty line instead of the first line.
sed -i.bak -e '/^$/!b' -e 'r./PATH/text.txt' -e :a -e '$!{' -e n -e ba -e } "$f"
This adds the boilerplate after the first empty line but perhaps that's acceptable. Refactoring it to replace it or add an empty line after should not be too challenging anyway. (Maybe use sed -n and instead explicitly print everything except the empty line.)
In brief terms, this skips to the end (simply prints) up until we find the first empty line. Then, we read and print the file, and go into a loop which prints the remainder of the file without returning to the beginning of the script.
sed that I think works. Uses files for the extra bit to be inserted.
b='##\n## comment piece\n##'
sed --posix -ne '
1,/^$/ {
/^$/ {
x;
/^true$/ !{
x
s/^$/true/
i\
'"$b"'
};
x;
s/^.*$//
}
}
p
' file1
with the examples using ranges of 1,/^$/, an empty first line would result in the disclaimer being printed twice. To avoid this, I've set it up to put a flag in the hold space ( x; s/^$/true/ ) that I can swap to the pattern space to check whether its the first blank. Once theres a match for blank line, i\ inserts the comment ($b) in front of the pattern space.
Thanks to ghoti for the initial plan.
I'm trying to use enscript to print PDFs from Mutt, and hitting character encoding issues. One way around them seems to be to just use sed to replace the problem characters: sed -ir 's/[“”]/"/g' {input}
My test input file is this:
“very dirty”
we’re
I'm hoping to get "very dirty" and we're but instead I'm still getting
â\200\234very dirtyâ\200\235
weâ\200\231re
I found a nice little post on printing to PDFs from Mutt that I used as a starting point. I have a bash script that I point to from my .muttrc with set print_command="$HOME/.mutt/print.sh" -- the script currently reads about like this:
#!/bin/bash
input="$1" pdir="$HOME/Desktop" open_pdf=evince
# Straighten out curly quotes
sed -ir 's/[“”]/"/g' $input
sed -ir "s/[’]/'/g" $input
tmpfile="`mktemp $pdir/mutt_XXXXXXXX.pdf`"
enscript --font=Courier8 $input -2r --word-wrap --fancy-header=mutt -p - 2>/dev/null | ps2pdf - $tmpfile
$open_pdf $tmpfile >/dev/null 2>&1 &
sleep 1
rm $tmpfile
It does a fine job of creating a PDF (and works fine if you give it a file as an argument) but I can't figure out how to fix the curly quotes.
I've tried a bunch of variations on the sed line:
input=sed -r 's/[“”]/"/g' $input
$input=sed -ir "s/[’]/'/g" $input
Per the suggestion at Can I use sed to manipulate a variable in bash? I also tried input=$(sed -r 's/[“”]/"/g' <<< $input) and I get an error: "Syntax error: redirection unexpected"
But none manages to actually change $input -- what is the correct syntax to change $input with sed?
Note: I accepted an answer that resolved the question I asked, but as you can see from the comments there are a couple of other issues here. enscript is taking in a whole file as a variable, not just the text of the file. So trying to tweak the text inside the file is going to take a few extra steps. I'm still learning.
On Editing Variables In General
BashFAQ #21 is a comprehensive reference on performing search-and-replace operations in bash, including within variables, and is thus recommended reading. On this particular case:
Use the shell's native string manipulation instead; this is far higher performance than forking off a subshell, launching an external process inside it, and reading that external process's output. BashFAQ #100 covers this topic in detail, and is well worth reading.
Depending on your version of bash and configured locale, it might be possible to use a bracket expression (ie. [“”], as your original code did). However, the most portable thing is to treat “ and ” separately, which will work even without multi-byte character support available.
input='“hello ’cruel’ world”'
input=${input//'“'/'"'}
input=${input//'”'/'"'}
input=${input//'’'/"'"}
printf '%s\n' "$input"
...correctly outputs:
"hello 'cruel' world"
On Using sed
To provide a literal answer -- you almost had a working sed-based approach in your question.
input=$(sed -r 's/[“”]/"/g' <<<"$input")
...adds the missing syntactic double quotes around the parameter expansion of $input, ensuring that it's treated as a single token regardless of how it might be string-split or glob-expanded.
But All That May Not Help...
The below is mentioned because your test script is manipulating content passed on the command line; if that's not the case in production, you can probably disregard the below.
If your script is invoked as ./yourscript “hello * ’cruel’ * world”, then information about exactly what the user entered is lost before the script is started, and nothing you can do here will fix that.
This is because $1, in that scenario, will only contain “hello; ’cruel’ and world” are in their own argv locations, and the *s will have been replaced with lists of files in the current directory (each such file substituted as a separate argument) before the script was even started. Because the shell responsible for parsing the user's command line (which is not the same shell running your script!) did not recognize the quotes as valid at the time when it ran this parsing, by the time the script is running, there's nothing you can do to recover the original data.
Abstract: The way to use sed to change a variable is explored, but what you really need is a way to use and edit a file. It is covered ahead.
Sed
The (two) sed line(s) could be solved with this (note that -i is not used, it is not a file but a value):
input='“very dirty”
we’re'
sed 's/[“”]/\"/g;s/’/'\''/g' <<<"$input"
But it should be faster (for small strings) to use the internals of the shell:
input='“very dirty”
we’re'
input=${input//[“”]/\"}
input=${input//[’]/\'}
printf '%s\n' "$input"
$1
But there is an underlying problem with your script, you are trying to clean an input received from the command line. You are using $1 as the source of the string. Once somebody writes:
./script “very dirty”
we’re
That input is lost. It is broken into shell's tokens and "$1" will be “very only.
But I do not believe that is what you really have.
file
However, you are also saying that the input comes from a file. If that is the case, then read it in with:
input="$(<infile)" # not $1
sed 's/[“”]/\"/g;s/’/'\''/g' <<<"$input"
Or, if you don't mind to edit (change) the file, do this instead:
sed -i 's/[“”]/\"/g;s/’/'\''/g' infile
input="$(<infile)"
Or, if you are clear and certain that what is being given to the script is a filename, like:
./script infile
You can use:
infile="$1"
sed -i 's/[“”]/\"/g;s/’/'\''/g' "$infile"
input="$(<"$infile")"
Other comments:
Then:
Quote your variables.
Do not use the very old `…` syntax, use $(…) instead.
Do not use variables in UPPER case, those are reserved for environment variables.
And (unless you actually meant sh) use a shebang (first line) that targets bash.
The command enscript most definitively requires a file, not a variable.
Maybe you should use evince to open the PS file, there is no need of the step to make a pdf, unless you know you really need it.
I believe that is better use a file to store the output of enscript and ps2pdf.
Do not hide the errors printed by the commands until everything is working as desired, then, just call the script as:
./script infile 2>/dev/null
Or as required to make it less verbose.
Final script.
If you call the script with the name of the file that enscript is going to use, something like:
./script infile
Then, the whole script will look like this (runs both in bash or sh):
#!/usr/bin/env bash
Usage(){ echo "$0; This script require a source file"; exit 1; }
[ $# -lt 1 ] && Usage
[ ! -e $1 ] && Usage
infile="$1"
pdir="$HOME/Desktop"
open_pdf=evince
# Straighten out curly quotes
sed -i 's/[“”]/\"/g;s/’/'\''/g' "$infile"
tmpfile="$(mktemp "$pdir"/mutt_XXXXXXXX.pdf)"
outfile="${tmpfile%.*}.ps"
enscript --font=Courier10 "$infile" -2r \
--word-wrap --fancy-header=mutt -p "$outfile"
ps2pdf "$outfile" "$tmpfile"
"$open_pdf" "$tmpfile" >/dev/null 2>&1 &
sleep 5
rm "$tmpfile" "$outfile"
this is my first post so hopefully I will make my question clear.
I am new to shell scripts and my task with this one is to add a new value to every line of a csv file. The value that needs added is based on the first 3 digits of the filename.
I bit of background. The csv files I am receiving are eventually being loaded into partitioned oracle tables. The start of the file name (e.g. BATTESTFILE.txt) contains the partitioned site so I need to write a script that takes the first 3 characters of the filename (in this example BAT) and add this to the end of each line of the file.
The closest I have got so far is when I stripped the code to the bare basics of what I need to do:
build_files()
{
OLDFILE=${filename[#]}.txt
NEWFILE=${filename[#]}.NEW.txt
ABSOLUTE='path/scripts/'
FULLOLD=$ABSOLUTE$OLDFILE
FULLNEW=$ABSOLUTE$NEWFILE
sed -e s/$/",${j}"/ "${FULLOLD}" > "${FULLNEW}"
}
set -A site 'BAT'
set -A filename 'BATTESTFILE'
for j in ${site[#]}; do
for i in ${filename[#]}; do
build_files ${j}
done
done
Here I have set up an array site as there will be 6 'sites' and this will make it easy to add additionals sits to the code as the files come through to me. The same is to be siad for the filename array.
This codes works, but it isn't as automated as I need. One of my most recent attempts has been below:
build_files()
{
OLDFILE=${filename[#]}.txt
NEWFILE=${filename[#]}.NEW.txt
ABSOLUTE='/app/dss/dsssis/sis/scripts/'
FULLOLD=$ABSOLUTE$OLDFILE
FULLNEW=$ABSOLUTE$NEWFILE
sed -e s/$/",${j}"/ "${FULLOLD}" > "${FULLNEW}"
}
set -A site 'BAT'
set -A filename 'BATTESTFILE'
for j in ${site[#]}; do
for i in ${filename[#]}; do
trust=echo "$filename" | cut -c1-3
echo "$trust"
if ["$trust" = 'BAT']; then
${j} = 'BAT'
fi
build_files ${j}
done
done
I found the code trust=echo "$filename" | cut -c1-3 through another question on StackOverflow as I was researching, but it doesn't seem to work for me. I added in the echo to test what trust was holding, but it was empty.
I am getting 2 errors back:
Line 17 - BATTESTFILE: not found
Line 19 - test: ] missing
Sorry for the long winded questions. Hopefully It contains helpful info and shows the steps I have taken. Any questions, comment away. Any help or guidance is very much appreciated. Thanks.
When you are new with shells, try avoiding arrays.
In an if statement use spaces before and after the [ and ] characters.
Get used to surrounding your shell variables with {} like ${trust}
I do not know how you fill your array, when the array is hardcoded, try te replace with
SITE=file1
SITE="${SITE} file2"
And you must tell unix you want to have the rightside eveluated with $(..) (better than backtics):
trust=$(echo "${filename}" | cut -c1-3)
Some guidelines and syntax help can be found at Google
Just use shell parameter expansion:
$ var=abcdefg
$ echo "${var:0:3}"
abc
Assuming you're using a reasonably capable shell like bash or ksh, for example
Just in case it is useful for anyone else now or in the future, I got my code to work as desired by using the below. Thanks Walter A below for his answer to my main problem of getting the first 3 characters from the filename and using them as a variable.
This gave me the desired output of taking the first 3 characters of the filename, and adding them to the end of each line in my csv file.
## Get the current Directory and file name, create a new file name
build_files()
{
OLDFILE=${i}.txt
NEWFILE=${i}.NEW.txt
ABSOLUTE='/app/dss/dsssis/sis/scripts/'
FULLOLD=$ABSOLUTE$OLDFILE
FULLNEW=$ABSOLUTE$NEWFILE
## Take the 3 characters from the filename and
## add them onto the end of each line in the csv file.
sed -e s/$/";${j}"/ "${FULLOLD}" > "${FULLNEW}"
}
## Loop to take the first 3 characters from the file names held in
## an array to be added into the new file above
set -A filename 'BATTESTFILE'
for i in ${filename[#]}; do
trust=$(echo "${i}" | cut -c1-3)
echo "${trust}"
j="${trust}"
echo "${i} ${j}"
build_files ${i} ${j}
done
Hope this is useful for someone else.
I am trying to prepare a bash script for use with gnu parallel. The script should take a filename, store the prefix of the file name as a describer, and store the row count (wc -l) as a numeric variable. Both if these become variables to use in a perl script. The describer is working fine.
But my storage of the number of rows, or my use of ${mm} is not generating a numeric variable that the perl script recognises. Any corrections appreciated.
#!/bin/bash
# Get the filename and strip suffix
sample=$1
describer=$(echo ${sample} | sed 's/.sync//')
echo ${describer} # works fine
# Get the number of rows
mm=$(cat ${sample} | wc -l)
echo ${mm} # works fine but is this a numeric variable?
# run the script using the variables;
# the ${mm} is where the perl script says its not numeric
perl script.pl --input ${describer}.sync --output ${describer}.genepop --region ${describer}:1-${mm}
This not an answer. I just wanted to rewrite your script in a better bash style. You know, you don't need to refer to variables with curly brackets all the time! E.g., $mm is fine enough, ${mm} is not needed in your cases. Moreover, your sed statement to strip the comment can be replaced with a bash equivalent. I added double quotes here and there so that you'll also be able to use everything with filenames that contain spaces and other funny symbols. I also removed the useless use of cat.
#!/bin/bash
# Get the filename and strip suffix
sample=$1
describer=${sample%.sync}
echo "$describer" # works fine
# Get the number of rows
mm=$(wc -l < "$sample")
echo "$mm" # works fine but is this a numeric variable?
# run the script using the variables;
# the $mm is where the perl script says its not numeric
perl script.pl --input "$sample" --output "$describer.genepop" --region "$describer:1-$mm"
Regarding your main problem: the problem might be in the perl program.
Regarding your question is this a numeric variable?, the answer is: variables have no types. They all are strings. Now read on:
Some versions of wc add spaces in front of the number they output, e.g.,
$ wc -l < file
42
(notice the spaces in front of 42). You should be able to notice if your version of wc behaves that way by running the version of the script I gave you (with the proper quotings). If you see some spaces in front of the number, that might be the cause of your problems.
If this is the case, you should replace the line
mm=$(wc -l < "$sample")
with
read mm < <(wc -l < "$sample")
Hope this helps!