#!/bin/bash # # cwhere # # find which C header file defines the requested macro/identifier # # Would normally do this in Perl, but doing it in pure bash for # something different. # # Mikel Ward # # TODO move cleanup trap inside print_definitions() name=cwhere trap cleanup EXIT # remove any temporary files, etc. # Usage: cleanup cleanup() { test -f "$sourcefile" && rm "$sourcefile" } get_cpp_output() { local headers="$@" sourcefile=$(mktemp) if test $? -ne 0; then echo "Error: Cannot create source file" 1>&2 exit 1 fi for header in $headers; do echo "#include <$header>" >> "$sourcefile" done cpp -dD "$sourcefile" if test $? -ne 0; then cat "$sourcefile" echo "Error: Cannot run cpp" 1>&2 exit 1 fi } # Print the definition of and which header file it appears in # Usage: print_definitions
... # Example: print_definitions O_WRONLY sys/types.h sys/stat.h fcntl.h print_definitions() { local identifier="$1" shift local headers="$@" local filenamepattern='^# [0-9]+ "([^"]*)"' local identifierpattern='^[[:space:]]*(#define|typedef)?[[:space:]]*'"$identifier"'([[:space:]]|\(|$)' get_cpp_output "$@" | while read -r line; do # this is evil, just doing it to learn something new # put the pattern in a variable to avoid bash treating pattern as a string :-( if [[ $line =~ $filenamepattern ]]; then file=${BASH_REMATCH[1]} if [[ $identifier == "INCLUDES" ]]; then echo "$file" else # just save it in case there's a symbol match : fi elif [[ $line =~ $identifierpattern ]]; then if [[ $identifier != "INCLUDES" ]]; then echo "$file: $line" fi fi done | grep -v '^/tmp/' | sort | uniq } # Usage: get_headers # Example: get_headers select # Example: get_headers 'open(2)' get_headers() { desc "$@" | sed -ne '/^[[:space:]]*#include/{s/^[[:space:]]*#include <\(.*\)>.*/\1/;p}' } # print a message saying how to run this program usage() { cat <&2 Usage: $name IDENTIFIER HEADER|FUNCTION [HEADER|FUNCTION]... Examples: $name O_RDONLY sys/stat.h sys/types.h fcntl.h $name O_RDONLY 'open(2)' $name 'struct sockaddr(|_in)?' sys/types.h sys/socket.h netinet/in.h $name 'struct sockaddr(|_in)?' 'socket(2)' 'ip(7)' EOF #Arguments: # IDENTIFIER is regex matching a C identifier # HEADER is a C include file, without the < > # FUNCTION is a function with an optional man section in parens } main() { if test $# -lt 2; then usage exit 2 fi # identifier is always the first argument... local args=("$1") # now look at the remaining arguments # they are either headers, which should be passed on verbatim # or are function/syscall names, in which case we have to find # which headers should be included according to the man pages shift i=1 for arg in "$@"; do # accept open() or open(2), convert to open or open(2) if [[ $arg =~ (.*)\((.*)\) ]]; then page=${BASH_REMATCH[1]} section=${BASH_REMATCH[2]} section="${section:+($section)}" headers=$(get_headers "$page$section") for header in $headers; do args[$i]=$header i=$((i+1)) done else args[$i]=$arg i=$((i+1)) fi done print_definitions "${args[@]}" } # only run the main script if it was executed # allows sourcing of the script to get function definitions # works out of the box in bash and ksh # zsh requires unsetopt functionargzero in ~/.zshenv case "$0" in *$name) main "$@" ;; esac