0
votes

I keep text files with definitions in a folder. I like to convert them to spoken word so I can listen to them. I already do this manually by running a few commands to insert some pre-processing codes into the text files and then convert the text to spoken word like so:

sed 's/\..*$/[[slnc 2000]]/' input.txt inserts a control code after first period

sed 's/$/[[slnc 2000]]/' input.txt" inserts a control code at end of each line

cat input.txt | say -v Alex -o input.aiff

Instead of having to retype these each time, I would like to create a Bash script that pipes the output of these commands to the final product. I want to call the script with the script name, followed by an input file argument for the text file. I want to preserve the original text file so that if I open it again, none of the control codes are actually inserted, as the only purpose of the control codes is to insert pauses in the audio file.

I've tried writing

#!/bin/bash 
FILE=$1
sed 's/$/ [[slnc 2000]]/' FILE -o FILE

But I get hung up immediately as it says sed: -o: No such file or directory. Can anyone help out?

2
You should write $FILE when you use the variable .Yuri Ginsburg
Some OSes and sed versions do not take kindly to reading from and writing to the same file. It might be better to either (a) precede this with cat "${FILE}" |, or (b) use a temp file and move it into place. The latter has an advantage that if the replacement fails for any reason, it'll likely fail before the original file is overwritten (and lost). The first method can overwrite the file. A third option is to do sed -i -e "s/..." "${FILE}", which does the changes in-place in the file ... though this does not always work, depending on the OS version, sed, etc.r2evans
(Or perhaps the "in place" limitation is a factor of the underlying filesystem, I'm not certain. I just know that I've run into situations where I could not use -i.)r2evans

2 Answers

1
votes

If you just want to use foo.txt to generate foo.aiff with control characters, you can do:

#!/bin/sh

for file; do
  test "${file%.txt}" = "${file}" && continue
  sed -e 's/\..*$/[[slnc 2000]]/' "$file" | 
    sed -e 's/$/[[slnc 2000]]/' | 
    say -v Alex -o "${file%.txt}".aiff
done

Call the script with your .txt files as arguments (eg, ./myscript *.txt) and it will generate the .aiff files. Be warned, if say overwrites files, then this will as well. You don't really need two sed invocations, and the sed that you're calling can be cleaned up, but I don't want to distract from the core issue here, so I'm leaving that as you have it.

1
votes

This will:-

a} Make a list of your text files to process in the current directory, with find.

b} Apply your sed commands to each text file in the list, but only for the current use, allowing you to preserve them intact.

c} Call "say" with the edited files.

I don't have say, so I can't test that or the control codes; but as long as you have Ed, the loop works. I've used it many times. I learned it as a result of exposure to FORTH, which is a language that still permits unterminated loops. I used to have problems with remembering to invoke next at the end of the script in order to start it, but I got over that by defining my words (functions) first, in FORTH style, and then always placing my single-use commands at the end.

#!/bin/sh

next() {
[[ -s stack ]] && main
end
}

main() {
line=$(ed -s stack < edprint+.txt)
infile=$(cat "${line}" | sed 's/\..*$/[[slnc 2000]]/' | sed 's/$/[[slnc 2000]]/')
say "${infile}" -v Alex -o input.aiff
ed -s stack < edpop+.txt
next
}

end() {
rm -v ./stack
rm -v ./edprint+.txt
rm -v ./edpop+.txt
exit 0
}

find *.txt -type -f > stack

cat >> edprint+.txt << EOF
1
q
EOF

cat >> edpop+.txt << EOF
1d
wq
EOF

next