2

I work as a grader and files submitted to me end up in a folder named with the student's name which is itself inside a specific folder on my linux share.

Currently I go through each source file and manually add a comment at the top of each file so that when I print them out I can know which files belong to which students.

What I want to do is recursively go through the homework folder and as I drop down into each student folder add a comment to the top of each file in that folder using the student directory name to know what to put into the comment.

This seems similar as far as adding comments at the top of the file but I'm not sure how to dynamically create the comment as I drop into each student folder.

Most recent update

Still doesn't work quite yet but here are the changes I made so you don't have to hunt for them.

I made some changes:

  • Single quotes in the printf statement are now double quotes since the ' in isn't was terminating it early.
  • Changed -writable to -perm 664 writable threw an error and all the files I'm trying to change are set at 644 when they get uploaded. If you know of a better way than that let me know.
  • If I understand exec correctly now $1 is undeclared (adding echo $1 in the exec argument confirmed this) so I added _filepath={} and removed the {} at the end of the exec argument.
  • Initialize _dirname with _filepath
  • In the two lines using regex to isolate the directory name the double quotes weren't closed. Now the _dirname successfully holds just the name of the directory afterwards
  • Now invokes ed with _filepath

I'm pretty sure the reason it doesn't work is because of single quotes in the line invoking ed are closing the exec argument.

current code:

student_head_action() {
   # We can't use parameter expansion on $PWD because of recursion
   local _dirname="${1%/*}"
   _dirname="${_dirname##*/}"

   [[ -d $1 ]] && return 0
   if ! [[ -w $1 && -f $1 ]]; then
      printf '%s\n' "$1 does not exist or is not writeable, skipping"
      return 1
   fi
   ed -s "$1" <<< $'0a\n'"//${_dirname}"$'\n.\n,s/\r//g\nw'
}

student_head() {
   local _file
   if (( $# )); then
      for _file; do
         student_head_action "${_file}" || _retval=1
      done
   else
      if shopt -qs globstar; then
         for _file in **/*; do
            student_head_action "${_file}" || _retval=1
         done
         shopt -u globstar
      else
         printf "%s\n" "Globstar isn't available, attempting to use GNU find ..."
         find . -type f -perm 644 -exec bash -c '
            _filepath={}
            _dirname=$_filepath
            _dirname="${_dirname%/*}"
            _dirname="${_dirname##*/}"
            echo $_dirname
            ed -s "$_filepath" <<< '"$'0a\n'"'"//${_dirname}"'"$'\n.\n,s/\r//g\nw'" \;
      fi
   fi
   return "${_retvalue-0}"
Matt
  • 123
  • 6
  • This is one of the times I wish two very good answers could both be accepted. Thank you both for helping me get this working! – Matt Sep 16 '11 at 18:30
  • As I said below, ~/.bashrc is not sourced by non-interactive shells, so find . -type f -exec bash -c 'student_head_action {}' \; will not work. – Chris Down Sep 16 '11 at 21:32
  • Those forward slashes are instead of the # because I'm commenting c++ code. Do they have to be escaped? – Matt Sep 16 '11 at 21:42
  • Are you sure you have any ^M's left in your file? I think it might be erroring because there is nothing to replace. – Chris Down Sep 16 '11 at 21:46
  • Pretty sure there are none left. I ran all the files through dos2unix. – Matt Sep 16 '11 at 22:19
  • There is your problem then. Use ed -s foobar <<< $'0a\n'"// ${_dirname}"$'\n.\nw' instead, it's erroring because there is nothing to replace. – Chris Down Sep 16 '11 at 22:55

2 Answers2

5

You can run student_head when in a folder to automatically add the folder name as a header to writeable files in it and its subdirectories:

student_head_action() {
    # We can't use parameter expansion on $PWD because of recursion
    local _dirname="${1%/*}"
    _dirname="${_dirname##*/}"

    [[ -d $1 ]] && return 0
    if ! [[ -w $1 && -f $1 ]]; then
        printf '%s\n' "$1 does not exist or is not writeable, skipping"
        return 1
    fi
    bash -c "ed -s {} <<< $'0a\n'"# Student: ${_dirname}"$'\n.\n,s/\r//g\nw'"
}

student_head() {

    local _file
    if (( $# )); then
        for _file; do
            student_head_action "${_file}" || _retval=1
        done
    else
        if shopt -s globstar > /dev/null 2>&1; then
            for _file in **/*; do
                student_head_action "${_file}" || _retval=1
            done
           shopt -u globstar
        else
            printf '%s\n' 'Globstar isn't available, attempting to use GNU find...'
            find . -type f -writable -exec bash -c '
                _dirname="${1%/*}\
                _dirname="${_dirname##*/}\
                ed -s "$1" <<< '"$'0a\n'"'"# Student: ${_dirname}"'"$'\n.\n,s/\r//g\nw'" {} \;
        fi                
    fi

    return "${_retval-0}"
}

Put these functions in your ~/.bashrc if you want student_head to always be accessible to your user when using bash.

student_head can be called in two ways:

  1. With no arguments. In this mode, all writeable files in the current directory and its subdirectories have the header prepended.
  2. With filenames as arguments. In this mode, only files listed in the arguments have the header prepended.

If a file in the arguments does not exist, student_head returns an exit status of 1.

It should be mentioned that this script uses globstar, which requires bash>=4.0.

Chris Down
  • 125,559
  • 25
  • 270
  • 266
  • Thank you for the fast response! I've worked my way through your code and I'm pretty sure I understand what it is doing at each step. This function only works from within the student directory and I want to be able to run it from level up and have it step down into each student directory and add the comments. – Matt Sep 16 '11 at 17:24
  • You probably are using an old version. I fixed the recursion bug a little while back. As for ^M (which is \r), I can't reproduce that. It probably indicates that you have ed misconfigured. – Chris Down Sep 16 '11 at 17:26
  • You read the comment too fast. The files originate from windows systems which is where most ^M problems originate. I didn't see them in the file prior to running the function but running dos2unix on the original file anyways fixed them showing up in the resulting file. – Matt Sep 16 '11 at 17:31
  • I added in a command to change \r\n to \n, and fixed a bug with the naming. – Chris Down Sep 16 '11 at 17:37
  • Ah. you changed the second "_dirname=..." I was trying to figure out what the point of the first one was. – Matt Sep 16 '11 at 17:39
  • Yeah, it was a bug. I was scratching my head for ages trying to work out why it was prepending the filename to the file... :) – Chris Down Sep 16 '11 at 17:40
  • Wonderful. The university linux system is using Bash 3.2.25. – Matt Sep 16 '11 at 18:00
  • Then they should upgrade it, even Debian stable is using 4.1.5(1)-release. Failing that, you could use find's -exec predicate. – Chris Down Sep 16 '11 at 18:11
  • ~/.bashrc is not read by non-interactive shells. I'll write a quick fix for shells that don't support globstar. – Chris Down Sep 16 '11 at 20:54
2

Here's a solution combining find and ed.

add_header() {
   declare regex IFS='|'
   (( $# )) && regex="^($*)$" || regex=""
   find "$PWD" -type f -exec bash -c ' 
      ( exec grep -E -qs -e "$0" <<<"${1##*/}" ) || exit
      _dirname="${1%/*}"
      _dirname="${_dirname##*/}"
      exec ed -s "${1}" <<EOF
0a
# Student: ${_dirname}
.
wq
EOF
   ' "${regex:-.+}" '{}' \;
   return 0
}

add_header                    # all files in current directory recursively
add_header file1 file2 file3  # only named files in current directory recursively
user89
  • 21
  • 1
  • find "$PWD" is pointless, just use find .. Also, you seem to misunderstand the use of exec (it shouldn't be used in the manner you are using it). There is also no need for wq, since EOF closes stdin (and as such closes ed). You also fail to account for failure -- specifically, at the minimum you should at least check that the file specified is both regular and writable. – Chris Down Sep 16 '11 at 18:28