Graipher
Graipher

Reputation: 7186

Prepend a # to the first line not already having a #

I have a file with options for a command I run. Whenever I run the command I want it to run with the options defined in the first line which is not commented out. I do this using this bash script:

while read run opt c; do
  [[ $run == \#* ]] && continue
  ./submit.py $opt $run -c "$c"
  break
done < to_submit.txt

The file to_submit.txt has entries like this:

#167993 options/optionfile.py long description
167995 options/other_optionfile.py other long description
...

After having run the submit script with the options in the last not commented out line, I want to comment out that line after the command ran successfully.

I can find the line number of the options I used adding this to the while loop:

line=$(grep -n "$run" to_submit.txt | grep "$opt" | grep "$c" | cut -f 1 -d ":")

But I'm not sure how to actually prepend a # to that line now. I could probably use head and tail to save the other lines and process that line separately and combine it all back into the file. But this sounds like it's to complicated, there must be an easier sed or awk solution to this.

Upvotes: 1

Views: 181

Answers (4)

Graipher
Graipher

Reputation: 7186

After some searching around I found that

awk -v run="$run" -v opt="$opt" '{if($1 == run && $2 == opt) {print "#" $0} else print}' to_submit.txt > temp
mv -b -f temp to_submit.txt

seems to solve this (without needing to find the line number first, just comparing $ run and $opt). This assumes that the combination of run and opt is enough to identify a line and the comment is not needed (which happens to be true in my case). Not sure how the comment which is spanning multiple fields in awk would also be taken into account.

Upvotes: -1

mklement0
mklement0

Reputation: 437833

Using GNU sed is probably simplest here:

sed '0,/^[^#]/ s//#&/' file

Add option -i if you want to update file in place.

  • '0,/^[^#]/ matches all lines up to and including the first one that doesn't start with #
  • s//#&/ then prepends # to that line.
    • Note that s//.../ (i.e., an empty regex) reuses the last matching regex in the range, which is /^[^#]/ in this case.

Note that the command doesn't work with BSD/OSX sed, unfortunately, because starting a range with 0 so as to allow the range endpoint to match the very first line also is not supported there. It is possible to make the command work with BSD/OSX sed, but it's more cumbersome.

Upvotes: 2

pjh
pjh

Reputation: 8084

If the input/output file is not very large, you can do it all in Bash:

optsfile=to_submit.txt

has_run_cmd=0
outputlines=()
while IFS= read -r inputline || [[ -n $inputline ]] ; do
    read run opt c <<<"$inputline"
    if (( has_run_cmd )) || [[ $run == \#* ]] ; then
        outputlines+=( "$inputline" )
    elif ./submit.py "$opt" "$run" -c "$c" ; then
        has_run_cmd=1
        outputlines+=( "#$inputline" )
    else
        exit $?
    fi
done < "$optsfile"

(( has_run_cmd )) && printf '%s\n' "${outputlines[@]}" > "$optsfile"

The lines of the file are put in the outputlines array, with a hash prepended to the line that was used in the ./submit.py command. If the command runs successfully, the file is overwritten with the lines in outputlines.

Upvotes: 0

Ed Morton
Ed Morton

Reputation: 203532

$ awk '!f && sub(/^[^#]/,"#&"){f=1} 1' file
#167993 options/optionfile.py long description
#167995 options/other_optionfile.py other long description
...

To overwrite the contents of the original file:

awk '!f && sub(/^[^#]/,"#&"){f=1} 1' file > tmp && mv tmp file

just like with any other UNIX command.

Upvotes: 4

Related Questions