0

I have the following if-else statements that checks to see if the i flag was added to the command and if so, write to the file from stdin:

 29 if [ $inplace -eq 1 ]; then
 30    tmp_file=$(mktemp)
 31    awk -v num="^$number" -v col="$column" -v value="$value" -F ',' \
 32       'BEGIN{OFS=FS}{ if ($0 ~ num) { $col=value; print } else print}' "$file" > "$tmp_file"
 33    mv "$tmp_file" "$file"
 34 else
 35    awk -v num="^$number" -v col="$column" -v value="$value" -F ',' \
 36       'BEGIN{OFS=FS}{ if ($0 ~ num) { $col=value; print } else print}' "$file"
 37 fi

but I'm getting the following error:

mv: cannot move '/tmp/tmp.ahALZC2Hgh' to '': No such file or directory

which means $file is empty and that makes sense to me because I read somewhere that stdin can only be used once and its being used by the awk cmd above

does anyone know how to hold onto the value? (without doing something like: var=$(cat), as I heard this was bad practice?)

or if this is a bad idea because and shouldn't be implemented, that works too!

the command that runs the file and receives the error is:

cat data.csv | ./update.sh -n 444 -c 2 -v "Alex" -i

and the i flag is for the user to signify the user wants changes to made to the original file in which case the variable $inplace is set to 1 in the following case while loop:

  3 while getopts f:n:c:v:i opt; do
  4     case $opt in
  5       f) file="$OPTARG";;
  6       n) number="$OPTARG";;
  7       c) column="$OPTARG";;
  8       v) value="$OPTARG";;
  9       i) inplace=1;;
 10       *) printf>&2 '%s\n' "Usage: $0 [-f file] [-n number] [-c column] [-v value]";;
 11     esac
 12 done

heres the entire script:

#!/bin/bash

while getopts f:n:c:v:i opt; do
    case $opt in
      f) file="$OPTARG";;
      n) number="$OPTARG";;
      c) column="$OPTARG";;
      v) value="$OPTARG";;
      i) inplace=1;;
      *) printf>&2 '%s\n' "Usage: $0 [-f file] [-n number] [-c column] [-v value]";;
    esac
done


if [ -z "$column" ]; then
   echo "Please specify a column number with -c." >&2
   exit 1
elif [ -z "$number" ]; then
   echo "Please specify a phone number to filter by via -n" >&2
   exit 1
elif [ -z "$value" ]; then
   echo "Please specify the new value via -v" >&2
   exit 1
elif [ "$column" -gt 4 ] || [ "$column" -lt 1 ]; then
       echo "this script can only handle cols1-4. for group editing, try ./groups.sh" >&2
       exit 1
fi    

if [ $inplace -eq 1 ]; then  
   tmp_file=$(mktemp) 
   awk -v num="^$number" -v col="$column" -v value="$value" -F ',' \
      'BEGIN{OFS=FS}{ if ($0 ~ num) { $col=value; print } else print}' "$file" > "$tmp_file"
   mv "$tmp_file" "$file"
else
   awk -v num="^$number" -v col="$column" -v value="$value" -F ',' \
      'BEGIN{OFS=FS}{ if ($0 ~ num) { $col=value; print } else print}' "$file"
fi


exit 0
1

1 Answer 1

3
  • Regarding your statement "so in this case a simple -i filename would be the best option as" - no, the best option is for the user to do cat foo | command > filename, not cat foo | command -i filename.
  • There is no "inplace" editing of stdin, you're just directing stdout to a file and that's what > filename already does.
  • Your -i option should not have a file name argument, that wouldn't make sense and would complicate your code. If there's no input file and someone calls your script with -i then report that as an error, just like sed does, e.g. try seq 3 | sed -i 's/2/4/' and it'll tell you sed: no input files
  • As written your code would trash your input file if awk encountered a failure. Use:
> "$tmp_file" &&
mv "$tmp_file" "$file"

instead of:

> "$tmp_file"
mv "$tmp_file" "$file"
  • { if ($0 ~ num) { $col=value; print } else print } = { if ($0 ~ num) { $col=value } print } which can be written idiomatically as just $0 ~ num { $col=value } 1.
  • You've removed the shift command I had provided again, thereby making the rest of your code harder to write again.
  • You should not have a -f file option, any file(s) you want to use as input should just come after the options+arguments being processed in your getopts loop and be addresses by "$@".
  • See also the other comments you received.

Try this (untested) as a starting point:

$ cat tst.sh
#!/usr/bin/env bash

tmp_file=$(mktemp) || exit 1
trap 'rm -f "$tmp_file"; exit' EXIT

while getopts n:c:v:i opt; do
    case $opt in
      n) number="$OPTARG";;
      c) column="$OPTARG";;
      v) value="$OPTARG";;
      i) inplace=1;;
      *) printf>&2 '%s\n' "Usage: $0 [-f file] [-n number] [-c column] [-v value]";;
    esac
done
shift $((OPTIND-1))

if [[ -z "$column" ]]; then
   echo "Please specify a column number with -c." >&2
   exit 1
elif [[ -z "$number" ]]; then
   echo "Please specify a phone number to filter by via -n" >&2
   exit 1
elif [[ -z "$value" ]]; then
   echo "Please specify the new value via -v" >&2
   exit 1
elif ! (( 1 <= column )) && (( column <= 4 )); then
   echo "this script can only handle cols1-4. for group editing, try ./groups.sh" >&2
   exit 1
fi

xform() {
   awk -v num="^$number" -v col="$column" -v value="$value" \
      'BEGIN{FS=OFS=","} $0 ~ num{$col=value} 1' "$@"
}

if [[ -n "$inplace" ]]; then
   if (( $# == 0 )); then
      echo "cannot do inplace editing as requested by -i without an input file present" >&2
      exit 1
   fi
   all_writeable=1
   for file; do
       if [[ ! -w "$file" ]]; then
          echo "file \"$file\" is not writeable" >&2
          all_writeable=0
       fi
   done
   if (( all_writeable == 0 )); then
       exit 1
   fi
   for file; do
       xform "$file" > "$tmp_file" &&
       mv "$tmp_file" "$file"
   done
else
   xform "$@"
fi

You should do more validation on the option arguments to ensure, for example, that the values like column that you expect to be a natural number truly are natural numbers.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .