├── LICENSE.txt ├── Makefile ├── README.md ├── realpath.sh └── t ├── test_canonicalize_path ├── test_readlink_emulation ├── test_realpath_integration └── test_resolve_symlinks /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Michael Kropat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | test: lint unit-test 3 | 4 | .PHONY: lint 5 | lint: 6 | -shellcheck realpath.sh 7 | -checkbashisms realpath.sh 8 | 9 | .PHONY: unit-test 10 | unit-test: t/* 11 | 12 | t/%: force 13 | bash "$@" 14 | dash "$@" 15 | 16 | .PHONY: force 17 | force: ; 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sh-realpath 2 | 3 | *A portable, pure shell implementation of realpath* 4 | 5 | Copy the functions in [realpath.sh](realpath.sh) into your shell script to 6 | avoid introducing a dependency on either `realpath` or `readlink -f`, since: 7 | 8 | * `realpath` does not come installed by default 9 | * `readlink -f` **is not portable** to OS-X ([relevant man page](https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man1/readlink.1.html)) 10 | 11 | ## Usage 12 | 13 | $ source ./realpath.sh 14 | $ realpath /proc/self 15 | /proc/2772 16 | 17 | Or we can get tricky: 18 | 19 | $ cd /tmp 20 | $ mkdir -p somedir/targetdir somedir/anotherdir 21 | $ ln -s somedir somedirlink 22 | $ ln -s somedir/anotherdir/../anotherlink somelink 23 | $ ln -s targetdir/targetpath somedir/anotherlink 24 | $ realpath .///somedirlink/././anotherdir/../../somelink 25 | /tmp/somedir/targetdir/targetpath 26 | 27 | ## API 28 | 29 | Note: unlike `realpath(1)`, these functions take no options; **do not** use `--` to escape any arguments 30 | 31 | | Function | Description 32 | | --------------------------------- | ------------- 33 | |
realpath PATH
| Resolve all symlinks to `PATH`, then output the canonicalized result 34 | |
resolve_symlinks PATH
| If `PATH` is a symlink, follow it as many times as possible; output the path of the first non-symlink found 35 | |
canonicalize_path PATH
| Output absolute path that `PATH` refers to, resolving any relative directories (`.`, `..`) in `PATH` and any symlinks in `PATH`'s ancestor directories 36 | 37 | ### readlink Emulation 38 | 39 | `realpath.sh` includes optional readlink emulation. It exposes a `readlink` 40 | function that calls the system `readlink(1)` if it exists. Otherwise it uses 41 | `stat(1)` to emulate the same functionality. In contrast to the functions in 42 | the previous section, you may pass `--` as the first argument, since you may be 43 | calling the system `readlink(1)`. 44 | -------------------------------------------------------------------------------- /realpath.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | realpath() { 4 | canonicalize_path "$(resolve_symlinks "$1")" 5 | } 6 | 7 | resolve_symlinks() { 8 | _resolve_symlinks "$1" 9 | } 10 | 11 | _resolve_symlinks() { 12 | _assert_no_path_cycles "$@" || return 13 | 14 | local dir_context path 15 | path=$(readlink -- "$1") 16 | if [ $? -eq 0 ]; then 17 | dir_context=$(dirname -- "$1") 18 | _resolve_symlinks "$(_prepend_dir_context_if_necessary "$dir_context" "$path")" "$@" 19 | else 20 | printf '%s\n' "$1" 21 | fi 22 | } 23 | 24 | _prepend_dir_context_if_necessary() { 25 | if [ "$1" = . ]; then 26 | printf '%s\n' "$2" 27 | else 28 | _prepend_path_if_relative "$1" "$2" 29 | fi 30 | } 31 | 32 | _prepend_path_if_relative() { 33 | case "$2" in 34 | /* ) printf '%s\n' "$2" ;; 35 | * ) printf '%s\n' "$1/$2" ;; 36 | esac 37 | } 38 | 39 | _assert_no_path_cycles() { 40 | local target path 41 | 42 | target=$1 43 | shift 44 | 45 | for path in "$@"; do 46 | if [ "$path" = "$target" ]; then 47 | return 1 48 | fi 49 | done 50 | } 51 | 52 | canonicalize_path() { 53 | if [ -d "$1" ]; then 54 | _canonicalize_dir_path "$1" 55 | else 56 | _canonicalize_file_path "$1" 57 | fi 58 | } 59 | 60 | _canonicalize_dir_path() { 61 | (cd "$1" 2>/dev/null && pwd -P) 62 | } 63 | 64 | _canonicalize_file_path() { 65 | local dir file 66 | dir=$(dirname -- "$1") 67 | file=$(basename -- "$1") 68 | (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file") 69 | } 70 | 71 | # Optionally, you may also want to include: 72 | 73 | ### readlink emulation ### 74 | 75 | readlink() { 76 | if _has_command readlink; then 77 | _system_readlink "$@" 78 | else 79 | _emulated_readlink "$@" 80 | fi 81 | } 82 | 83 | _has_command() { 84 | hash -- "$1" 2>/dev/null 85 | } 86 | 87 | _system_readlink() { 88 | command readlink "$@" 89 | } 90 | 91 | _emulated_readlink() { 92 | if [ "$1" = -- ]; then 93 | shift 94 | fi 95 | 96 | _gnu_stat_readlink "$@" || _bsd_stat_readlink "$@" 97 | } 98 | 99 | _gnu_stat_readlink() { 100 | local output 101 | output=$(stat -c %N -- "$1" 2>/dev/null) && 102 | 103 | printf '%s\n' "$output" | 104 | sed "s/^‘[^’]*’ -> ‘\(.*\)’/\1/ 105 | s/^'[^']*' -> '\(.*\)'/\1/" 106 | # FIXME: handle newlines 107 | } 108 | 109 | _bsd_stat_readlink() { 110 | stat -f %Y -- "$1" 2>/dev/null 111 | } 112 | -------------------------------------------------------------------------------- /t/test_canonicalize_path: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . ./realpath.sh 4 | 5 | setUp() { 6 | _test_previousdir=$PWD 7 | _test_workingdir=$(mktemp -d -t sh-realpath.XXXXXX) 8 | cd -P -- "$_test_workingdir" 9 | } 10 | 11 | tearDown() { 12 | cd -- "$_test_previousdir" 13 | rm -rf -- "$_test_workingdir" 14 | } 15 | 16 | it_outputs_pwd_when_passed_zero_args() { 17 | local output 18 | 19 | output=$(canonicalize_path) 20 | 21 | assertTrue '`canonicalize_path` succeeds' $? 22 | assertEquals 'outputs $PWD' "$PWD/" "$output" 23 | } 24 | 25 | it_outputs_absolute_path_to_target_when_passed_the_name_of_a_non_existent_file() { 26 | local output 27 | 28 | output=$(canonicalize_path non_existent_file) 29 | 30 | assertTrue '`canonicalize_path non_existent_file` succeeds' $? 31 | assertEquals 'output is "$PWD/non_existent_file"' "$PWD/non_existent_file" "$output" 32 | } 33 | 34 | it_returns_an_error_when_passed_a_path_in_a_non_existent_dir() { 35 | local output 36 | 37 | output=$(canonicalize_path non_existent_dir/somepath 2>&1) 38 | 39 | assertFalse '`canonicalize_path non_existent_dir/somepath` fails' $? 40 | assertNull 'no output' "$output" 41 | } 42 | 43 | it_outputs_absolute_path_when_passed_the_name_of_a_path_in_a_subdir() { 44 | local output 45 | mkdir somedir 46 | 47 | output=$(canonicalize_path somedir/somepath) 48 | 49 | assertTrue '`canonicalize_path somedir/somepath` succeeds' $? 50 | assertEquals 'output is "$PWD/somedir/somepath"' "$PWD/somedir/somepath" "$output" 51 | } 52 | 53 | it_returns_an_error_when_passed_a_path_in_a_symlink_to_nowhere() { 54 | ln -s non/existent/dir somelink 55 | 56 | canonicalize_path somelink/somepath 57 | 58 | assertFalse '`canonicalize_path somelink/somepath` fails' $? 59 | } 60 | 61 | it_returns_the_target_dir_when_passed_a_symlink_to_another_dir() { 62 | local output 63 | mkdir somedir 64 | ln -s somedir somelink 65 | 66 | output=$(canonicalize_path somelink) 67 | 68 | assertTrue '`canonicalize_path somelink` succeeds' $? 69 | assertEquals 'output is "$PWD/somedir"' "$PWD/somedir" "$output" 70 | } 71 | 72 | it_returns_absolute_path_of_target_directory_when_passed_a_path_in_a_symlink_to_another_dir() { 73 | local output 74 | mkdir somedir 75 | ln -s somedir somelink 76 | 77 | output=$(canonicalize_path somelink/somepath) 78 | 79 | assertTrue '`canonicalize_path somelink/somepath` succeeds' $? 80 | assertEquals 'output is "$PWD/somedir/somepath"' "$PWD/somedir/somepath" "$output" 81 | } 82 | 83 | it_collapses_current_dir_references() { 84 | local output 85 | 86 | output=$(canonicalize_path ././somepath) 87 | 88 | assertTrue '`canonicalize_path ././somepath` succeeds' $? 89 | assertEquals 'output is "$PWD/somepath"' "$PWD/somepath" "$output" 90 | } 91 | 92 | it_collapses_extra_slashes() { 93 | local output 94 | 95 | output=$(canonicalize_path .///somepath) 96 | 97 | assertTrue '`canonicalize_path .///somepath` succeeds' $? 98 | assertEquals 'output is "$PWD/somepath"' "$PWD/somepath" "$output" 99 | } 100 | 101 | it_collapses_parent_dir_references() { 102 | local output 103 | mkdir -p somedir/anotherdir 104 | 105 | output=$(canonicalize_path somedir/anotherdir/../../somepath) 106 | 107 | assertTrue '`canonicalize_path somedir/anotherdir/../../somepath` succeeds' $? 108 | assertEquals 'output is "$PWD/somepath"' "$PWD/somepath" "$output" 109 | } 110 | 111 | 112 | ##### Test Harness ##### 113 | 114 | # suite() -- find and register tests to be run 115 | # Derived from Gary Bernhardt's screencast #68 116 | # (https://www.destroyallsoftware.com/screencasts/catalog/test-driving-shell-scripts) 117 | suite() { 118 | local name tests 119 | tests=$(grep ^it_ "$0" | cut -d '(' -f 1) 120 | for name in $tests; do 121 | suite_addTest "$name" 122 | done 123 | } 124 | 125 | if hash shunit2 2>/dev/null; then 126 | . shunit2 127 | else 128 | echo 'Error: shunit2(1) could not be located. Please install it on your $PATH.' >&2 129 | exit 1 130 | fi 131 | -------------------------------------------------------------------------------- /t/test_readlink_emulation: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . ./realpath.sh 4 | 5 | setUp() { 6 | unset _system_readlink _gnu_stat_readlink _bsd_stat_readlink 7 | 8 | _has_command() { 9 | return 1 10 | } 11 | } 12 | 13 | it_calls_system_readlink_when_has_command_readlink_is_true() { 14 | _has_command() { 15 | test "$1" = readlink 16 | } 17 | 18 | local readlink_arg1 readlink_arg2 19 | _system_readlink() { 20 | readlink_arg1="$1" 21 | readlink_arg2="$2" 22 | } 23 | 24 | readlink -- some/path 25 | 26 | assertEquals -- "$readlink_arg1" 27 | assertEquals some/path "$readlink_arg2" 28 | } 29 | 30 | it_calls__gnu_stat_readlink_when_has_command_readlink_is_false() { 31 | local called 32 | _gnu_stat_readlink() { 33 | called=1 34 | } 35 | 36 | readlink -- some/path 37 | 38 | assertNotNull "_gnu_stat_readlink called" "$called" 39 | } 40 | 41 | it_passes_actual_arg_to__gnu_stat_readlink() { 42 | local arg 43 | _gnu_stat_readlink() { 44 | arg="$1" 45 | } 46 | 47 | readlink -- some/path 48 | 49 | assertEquals some/path "$arg" 50 | } 51 | 52 | it_passes_first_arg_to__gnu_stat_readlink_when_no_dashes() { 53 | local arg 54 | _gnu_stat_readlink() { 55 | arg="$1" 56 | } 57 | 58 | readlink some/path 59 | 60 | assertEquals some/path "$arg" 61 | } 62 | 63 | it_doesnt_call__bsd_stat_readlink_when__gnu_stat_readlink_returns_true() { 64 | _gnu_stat_readlink() { 65 | return 0 66 | } 67 | 68 | local called 69 | _bsd_stat_readlink() { 70 | called=1 71 | } 72 | 73 | readlink -- some/path 74 | 75 | assertNull "_bsd_stat_readlink not called" "$called" 76 | } 77 | 78 | it_calls__bsd_stat_readlink_when__gnu_stat_readlink_returns_false() { 79 | _gnu_stat_readlink() { 80 | return 1 81 | } 82 | 83 | local called 84 | _bsd_stat_readlink() { 85 | called=1 86 | } 87 | 88 | readlink -- some/path 89 | 90 | assertNotNull "_bsd_stat_readlink called" "$called" 91 | } 92 | 93 | it_passes_actual_arg_to__bsd_stat_readlink() { 94 | _gnu_stat_readlink() { 95 | return 1 96 | } 97 | 98 | local arg 99 | _bsd_stat_readlink() { 100 | arg="$1" 101 | } 102 | 103 | readlink -- some/path 104 | 105 | assertEquals some/path "$arg" 106 | } 107 | 108 | ##### Test Harness ##### 109 | 110 | # suite() -- find and register tests to be run 111 | # Derived from Gary Bernhardt's screencast #68 112 | # (https://www.destroyallsoftware.com/screencasts/catalog/test-driving-shell-scripts) 113 | suite() { 114 | local name tests 115 | tests=$(grep ^it_ "$0" | cut -d '(' -f 1) 116 | for name in $tests; do 117 | suite_addTest "$name" 118 | done 119 | } 120 | 121 | if hash shunit2 2>/dev/null; then 122 | . shunit2 123 | else 124 | echo 'Error: shunit2(1) could not be located. Please install it on your $PATH.' >&2 125 | exit 1 126 | fi 127 | -------------------------------------------------------------------------------- /t/test_realpath_integration: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . ./realpath.sh 4 | 5 | setUp() { 6 | _test_previousdir=$PWD 7 | _test_workingdir=$(mktemp -d -t sh-realpath.XXXXXX) 8 | cd -P -- "$_test_workingdir" 9 | } 10 | 11 | tearDown() { 12 | cd -- "$_test_previousdir" 13 | rm -rf -- "$_test_workingdir" 14 | } 15 | 16 | it_outputs_pwd_when_passed_zero_args() { 17 | local output 18 | 19 | output=$(realpath) 20 | 21 | assertTrue '`realpath` succeeds' $? 22 | assertEquals 'outputs "$PWD/"' "$PWD/" "$output" 23 | } 24 | 25 | it_outputs_the_canonical_path_of_crazy_paths() { 26 | local output 27 | mkdir -p somedir/targetdir somedir/anotherdir 28 | ln -s somedir somedirlink 29 | ln -s somedir/anotherdir/../anotherlink somelink 30 | ln -s targetdir/targetpath somedir/anotherlink 31 | 32 | output=$(realpath .///somedirlink/././anotherdir/../../somelink) 33 | 34 | assertTrue '`realpath .///somedirlink/././anotherdir/../../somelink` succeeds' $? 35 | assertEquals 'outputs "$PWD/somedir/targetdir/targetpath"' "$PWD/somedir/targetdir/targetpath" "$output" 36 | } 37 | 38 | ##### Test Harness ##### 39 | 40 | # suite() -- find and register tests to be run 41 | # Derived from Gary Bernhardt's screencast #68 42 | # (https://www.destroyallsoftware.com/screencasts/catalog/test-driving-shell-scripts) 43 | suite() { 44 | local name tests 45 | tests=$(grep ^it_ "$0" | cut -d '(' -f 1) 46 | for name in $tests; do 47 | suite_addTest "$name" 48 | done 49 | } 50 | 51 | if hash shunit2 2>/dev/null; then 52 | . shunit2 53 | else 54 | echo 'Error: shunit2(1) could not be located. Please install it on your $PATH.' >&2 55 | exit 1 56 | fi 57 | -------------------------------------------------------------------------------- /t/test_resolve_symlinks: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . ./realpath.sh 4 | 5 | setUp() { 6 | _test_previousdir=$PWD 7 | _test_workingdir=$(mktemp -d -t sh-realpath.XXXXXX) 8 | cd -P -- "$_test_workingdir" 9 | } 10 | 11 | tearDown() { 12 | cd -- "$_test_previousdir" 13 | rm -rf -- "$_test_workingdir" 14 | } 15 | 16 | it_outputs_nothing_when_passed_nothing() { 17 | local output 18 | output=$(resolve_symlinks) 19 | 20 | assertTrue '`resolve_symlinks` succeeds' $? 21 | assertNull 'there is no output' "$output" 22 | } 23 | 24 | it_outputs_filename_when_passed_a_nonexistent_filename() { 25 | local output 26 | output=$(resolve_symlinks non_existent_file) 27 | 28 | assertTrue '`resolve_symlinks non_existent_file` succeeds' $? 29 | assertEquals 'output is "non_existent_file"' non_existent_file "$output" 30 | } 31 | 32 | it_outputs_filename_when_passed_a_file_that_exists() { 33 | : >|somefile 34 | 35 | output=$(resolve_symlinks somefile) 36 | 37 | assertTrue '`resolve_symlinks somefile` succeeds' $? 38 | assertEquals 'output is "somefile"' somefile "$output" 39 | } 40 | 41 | it_outputs_the_target_when_passed_a_symlink_to_a_file_that_doesnt_exist() { 42 | local output 43 | ln -s non_existent_file somelink 44 | 45 | output=$(resolve_symlinks somelink) 46 | 47 | assertTrue '`resolve_symlinks somelink` succeeds' $? 48 | assertEquals 'output is "non_existent_file"' non_existent_file "$output" 49 | } 50 | 51 | it_outputs_the_target_when_passed_a_symlink_to_a_file_that_exists() { 52 | local output 53 | ln -s somefile somelink 54 | : >|somefile 55 | 56 | output=$(resolve_symlinks somelink) 57 | 58 | assertTrue '`resolve_symlinks somelink` succeeds' $? 59 | assertEquals 'output is "somefile"' somefile "$output" 60 | } 61 | 62 | it_outputs_the_target_when_passed_a_symlink_in_a_child_dir() { 63 | local output 64 | mkdir somedir 65 | ln -s somepath somedir/somelink 66 | 67 | output=$(resolve_symlinks somedir/somelink) 68 | 69 | assertTrue '`resolve_symlinks somedir/somelink` succeeds' $? 70 | assertEquals 'output is "somedir/somepath"' somedir/somepath "$output" 71 | } 72 | 73 | it_outputs_the_final_target_when_passed_a_symlink_to_a_symlink_to_a_file() { 74 | local output 75 | ln -s anotherlink somelink 76 | ln -s somepath anotherlink 77 | 78 | output=$(resolve_symlinks somelink) 79 | 80 | assertTrue '`resolve_symlinks somelink` succeeds' $? 81 | assertEquals 'output is "somepath"' somepath "$output" 82 | } 83 | 84 | it_outputs_the_final_target_with_path_when_passed_a_symlink_to_a_file_in_another_dir() { 85 | local output 86 | ln -s somedir/somepath somelink 87 | 88 | output=$(resolve_symlinks somelink) 89 | 90 | assertTrue '`resolve_symlinks somelink` succeeds' $? 91 | assertEquals 'output is "somedir/somepath"' somedir/somepath "$output" 92 | } 93 | 94 | it_outputs_the_final_target_with_path_when_passed_a_symlink_to_a_symlink_in_another_dir() { 95 | local output 96 | ln -s somedir/anotherlink somelink 97 | mkdir somedir 98 | ln -s somepath somedir/anotherlink 99 | 100 | output=$(resolve_symlinks somelink) 101 | 102 | assertTrue '`resolve_symlinks somelink` succeeds' $? 103 | assertEquals 'output is "somedir/somepath"' somedir/somepath "$output" 104 | } 105 | 106 | it_outputs_a_valid_path_to_the_current_dir_when_passed_a_symlink_that_has_trailing_dots() { 107 | local output 108 | ln -s somepath/.. somelink 109 | 110 | output=$(resolve_symlinks somelink) 111 | 112 | assertTrue '`resolve_symlinks somelink` succeeds' $? 113 | assertEquals 'output is "somepath/.."' somepath/.. "$output" 114 | } 115 | 116 | it_outputs_a_valid_path_to_the_final_target_when_passed_a_symlink_that_references_the_parent_dir() { 117 | local output 118 | mkdir somedir 119 | ln -s ../somepath somedir/somelink 120 | 121 | output=$(resolve_symlinks somedir/somelink) 122 | 123 | assertTrue '`resolve_symlinks somedir/somelink` succeeds' $? 124 | assertEquals 'output is "somedir/../somepath"' somedir/../somepath "$output" 125 | } 126 | 127 | it_outputs_the_absolute_path_when_passed_a_symlink_to_an_absolute_path() { 128 | local output 129 | ln -s /some/absolute/path somelink 130 | 131 | output=$(resolve_symlinks somelink) 132 | 133 | assertTrue '`resolve_symlinks somelink` succeeds' $? 134 | assertEquals 'output is "/some/absolute/path"' /some/absolute/path "$output" 135 | } 136 | 137 | it_outputs_the_final_target_when_passed_a_symlink_to_an_absolute_path_symlink() { 138 | local output 139 | ln -s $PWD/anotherlink somelink 140 | ln -s somepath anotherlink 141 | 142 | output=$(resolve_symlinks somelink) 143 | 144 | assertTrue '`resolve_symlinks somelink` succeeds' $? 145 | assertEquals 'output is "$PWD/somepath"' $PWD/somepath "$output" 146 | } 147 | 148 | it_returns_an_error_when_passed_a_symlink_to_a_symlink_to_a_symlink_that_points_to_the_first_symlink() { 149 | ln -s anotherlink somelink 150 | ln -s circularlink anotherlink 151 | ln -s somelink circularlink 152 | 153 | resolve_symlinks somelink 154 | 155 | assertFalse '`resolve_symlinks somelink` fails' $? 156 | } 157 | 158 | 159 | ##### Test Harness ##### 160 | 161 | # suite() -- find and register tests to be run 162 | # Derived from Gary Bernhardt's screencast #68 163 | # (https://www.destroyallsoftware.com/screencasts/catalog/test-driving-shell-scripts) 164 | suite() { 165 | local name tests 166 | tests=$(grep ^it_ "$0" | cut -d '(' -f 1) 167 | for name in $tests; do 168 | suite_addTest "$name" 169 | done 170 | } 171 | 172 | if hash shunit2 2>/dev/null; then 173 | . shunit2 174 | else 175 | echo 'Error: shunit2(1) could not be located. Please install it on your $PATH.' >&2 176 | exit 1 177 | fi 178 | --------------------------------------------------------------------------------