Skip to content

Commit e874682

Browse files
committed
Version 1.0
1 parent 24670b8 commit e874682

12 files changed

Lines changed: 533 additions & 1 deletion

File tree

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
11
# slice
2-
Commandline tool for slicing files based on line-number or content expression
2+
Command line tool for slicing files based on line-number or content expression
3+
4+
Run `slice --help` for information and example of usage.
5+
6+
slice is written in bash and assumes bash 4 or later.
7+
8+
The tool is located in `bin/`.
9+
10+
## Installation
11+
Clone the repository and place `bin/slice` somewhere on your PATH.
12+
13+
## Development
14+
To run the tests, run `test/run_test.sh`.

bin/slice

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
#!/bin/bash
2+
#
3+
# (C) DevConSoft 2018
4+
# Author: Per Böhlin
5+
#
6+
# Source: https://github.com/devconsoft/slice
7+
#
8+
9+
set -o errexit -o nounset -o pipefail
10+
11+
# --- Usage and version ---
12+
13+
usage() {
14+
cat <<EOF
15+
Usage:
16+
$SCRIPT -h|--help
17+
$SCRIPT --version
18+
$SCRIPT [-a|--text] [-n|--line-number] START_MARKER END_MARKER [FILE]
19+
20+
START_MARKER and END_MARKER is either a grep regular expression, or a line-number
21+
prefixed with dash(-). A standalone dash(-) indicates beginning/end of file.
22+
23+
if FILE is omitted, content is read from stdin.
24+
25+
Options:
26+
-h, --help output this small usage guide and exit
27+
--version output version information and exit
28+
-a, --text force text mode
29+
-n, --line-number print line number with output lines
30+
-B, --before-context=NUM print NUM lines of leading context
31+
-A, --after-context=NUM print NUM lines of trailing context
32+
-C, --context=NUM print NUM lines of output context
33+
34+
Examples:
35+
\$ $SCRIPT -2 foo file.txt
36+
Slice the file file.txt starting on line 2 ending with first occurrance
37+
after line 2 of a line matching 'foo' .
38+
39+
\$ expression | $SCRIPT -n -B 1 -A 2 a z
40+
Slice the input from stdin. First line shown will be 1 line before
41+
first line that matches 'a'; ending 2 lines after first
42+
occurance of 'z', and print line numbers.
43+
EOF
44+
}
45+
46+
version() {
47+
echo "$SCRIPT -- version $VERSION"
48+
echo "Copyright DevConSoft, 2018"
49+
}
50+
51+
# --- Globals and cleanup ---
52+
53+
SCRIPT="$(basename "${BASH_SOURCE[0]}" )"
54+
VERSION=1.0
55+
START_MARKER=
56+
END_MARKER=
57+
INPUT_FILE=
58+
INPUT_CONTENT=
59+
PRINT_LINE_NUMBER=false
60+
BEFORE_CONTEXT=0
61+
AFTER_CONTEXT=0
62+
declare -a GREP_CMD=(grep)
63+
64+
# --- Commandline parsing ---
65+
if ! ARGS=$(getopt -o hanB:A:C: -l "help,version,line-number,before-context,after-context,context" -n "$SCRIPT" -- "$@"); then
66+
echo "$@"
67+
usage
68+
exit 2
69+
fi
70+
71+
eval set -- "$ARGS";
72+
73+
while true; do
74+
case $1 in
75+
-h|--help)
76+
shift;
77+
usage
78+
exit 0;
79+
;;
80+
--version)
81+
shift;
82+
version;
83+
exit 0;
84+
;;
85+
-a|--text)
86+
shift;
87+
GREP_CMD+=('--text')
88+
;;
89+
-n|--line-number)
90+
shift;
91+
PRINT_LINE_NUMBER=true
92+
;;
93+
-A|--after-context)
94+
AFTER_CONTEXT=$2;
95+
shift 2;
96+
;;
97+
-B|--before-context)
98+
BEFORE_CONTEXT=$2;
99+
shift 2;
100+
;;
101+
-C|--context)
102+
BEFORE_CONTEXT=$2;
103+
AFTER_CONTEXT=$2;
104+
shift 2;
105+
;;
106+
--)
107+
shift;
108+
break;
109+
;;
110+
esac
111+
done
112+
113+
# --- Functions ---
114+
is_int() {
115+
if [ $1 -eq $1 ] 2>/dev/null; then
116+
return 0
117+
else
118+
return 1
119+
fi
120+
}
121+
122+
# $1 start marker
123+
get_start_line() {
124+
local start_marker=$1
125+
126+
if is_int "${start_marker}"; then
127+
sline=${start_marker}
128+
elif [ "${start_marker}" != "-" ] ; then
129+
sline=$("${GREP_CMD[@]}" -m 1 -n "${start_marker}" <(echo "${INPUT_CONTENT}")|cut -d: -f1) || { echo "start marker '${start_marker}' not found in file" && exit 1; }
130+
else
131+
sline=1
132+
fi
133+
echo "${sline}"
134+
}
135+
136+
# $1 end marker
137+
# $2 start line
138+
get_end_line() {
139+
local end_marker="$1"
140+
local start_line="$2"
141+
142+
if is_int "${end_marker}"; then
143+
eline=end_marker
144+
elif [ "${end_marker}" != "-" ] ; then
145+
eline=$("${GREP_CMD[@]}" -m 1 -n "${end_marker}" <(echo "${INPUT_CONTENT}"|tail -n+${start_line})|cut -d: -f1) || { echo "end marker '${end_marker}' not found in file" && exit 1; }
146+
((eline+=${start_line}-1))
147+
else
148+
eline=$(wc -l <(echo "${INPUT_CONTENT}")|cut -s -d " " -f1)
149+
fi
150+
echo "${eline}"
151+
}
152+
153+
slice() {
154+
local start_marker="$1"
155+
local end_marker="$2"
156+
local sline
157+
local eline
158+
159+
sline="$(get_start_line "${start_marker}")"
160+
eline="$(get_end_line "${end_marker}" "${sline}")"
161+
162+
((sline-=${BEFORE_CONTEXT}))
163+
((eline++))
164+
((eline+=${AFTER_CONTEXT}))
165+
linediff="$((eline-sline))"
166+
if $PRINT_LINE_NUMBER; then
167+
cat -n <(echo "${INPUT_CONTENT}") | tail -n+${sline} |head -n${linediff}
168+
else
169+
tail -n+${sline} <(echo "${INPUT_CONTENT}")|head -n${linediff}
170+
fi
171+
}
172+
173+
# --- Execution ---
174+
175+
START_MARKER="$1"
176+
END_MARKER="$2"
177+
INPUT_FILE="${3:-}"
178+
179+
if [ -n "${INPUT_FILE}" ] && [ ! -f "${INPUT_FILE}" ]; then
180+
echo "Input file '${INPUT_FILE}' does not exist."
181+
exit 4
182+
fi
183+
184+
if [ -n "${INPUT_FILE}" ]; then
185+
INPUT_CONTENT=$(<"${INPUT_FILE}")
186+
else
187+
INPUT_CONTENT=$(</dev/stdin)
188+
fi
189+
190+
191+
slice "${START_MARKER}" "${END_MARKER}"

test/assert.sh

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/bin/bash
2+
3+
ASSERT_LAST_OUTPUT=
4+
5+
# start test
6+
stest() {
7+
local testname=$1
8+
echo -n "${testname}: "
9+
}
10+
11+
# end test
12+
etest() {
13+
echo "pass"
14+
}
15+
16+
# fail test
17+
ftest() {
18+
echo "FAIL"
19+
}
20+
21+
# $1 expression
22+
# $2 exit code
23+
# $3 expected exit code
24+
print_outcome() {
25+
local expression=$1
26+
local exit_code=$2
27+
local expected_exit_code=$3
28+
echo ""
29+
echo "----------------------"
30+
echo "Expression: '${expression}'"
31+
echo "exit code (expected): ${exit_code} (${expected_exit_code})"
32+
echo "with output:"
33+
echo "${ASSERT_LAST_OUTPUT}"
34+
echo "----------------------"
35+
}
36+
37+
# $1 match
38+
print_match() {
39+
local match=$1
40+
echo ""
41+
echo "----------------------"
42+
echo "Match: ${match}"
43+
echo "Outout:"
44+
echo "${ASSERT_LAST_OUTPUT}"
45+
echo "----------------------"
46+
}
47+
48+
# $1 expression
49+
# $2 expected exit code
50+
assert_expr() {
51+
local expression="$1"
52+
local expected_exit_code=${2:-0}
53+
local exit_code=0
54+
ASSERT_LAST_OUTPUT=$(eval "${expression}") || exit_code=$?
55+
if [ "${exit_code}" != "${expected_exit_code}" ]; then
56+
print_outcome "${expression}" "${exit_code}" "${expected_exit_code}"
57+
ftest
58+
return ${exit_code}
59+
elif [ -n "${TEST_DEBUG:-}" ]; then
60+
print_outcome "${expression}" "${exit_code}" "${expected_exit_code}"
61+
fi
62+
63+
return 0
64+
}
65+
66+
# $1 match
67+
# $2 expression
68+
# $3 expected exit code =0
69+
assert_in_output_expr() {
70+
local match="$1"
71+
local expression="$2"
72+
local expected_exit_code=${3:-0}
73+
74+
assert_expr "${expression}" "${expected_exit_code}"
75+
76+
if [[ ! "${ASSERT_LAST_OUTPUT}" =~ $match ]]; then
77+
print_match "${match}"
78+
return 1
79+
elif [ -n "${TEST_DEBUG:-}" ]; then
80+
print_match "${match}"
81+
fi
82+
return 0
83+
}
84+
85+
# $1 first
86+
# $2 second
87+
assert_equal() {
88+
if [ -n "${TEST_DEBUG:-}" ]; then
89+
diff -y <(echo "$1") <(echo "$2")
90+
else
91+
diff -u <(echo "$1") <(echo "$2")
92+
fi
93+
}
94+
95+
# $1 expected_output
96+
# $2 expression
97+
# $3 expected exit code
98+
assert_equal_output_expr() {
99+
local expected_output="$1"
100+
local expression="$2"
101+
local expected_exit_code=${3:-0}
102+
103+
assert_expr "${expression}" "${expected_exit_code}"
104+
assert_equal "${expected_output}" "${ASSERT_LAST_OUTPUT}"
105+
}
106+
107+
108+
self_test() {
109+
stest "Test framework self-tests"
110+
test "assert_expr 'true'"
111+
assert_expr 'assert_expr true'
112+
! assert_expr 'false' > /dev/null || return 1
113+
assert_in_output_expr 'FOO' 'echo FOO'
114+
! assert_in_output_expr 'FOO' 'false' > /dev/null || return 1
115+
116+
IFS='' read -r -d '' ml1 <<'EOF' || true
117+
line 1
118+
line 2
119+
EOF
120+
121+
IFS='' read -r -d '' ml2 <<'EOF' || true
122+
line 1
123+
line 2
124+
line 3
125+
EOF
126+
127+
assert_equal "${ml1}" "${ml1}"
128+
! assert_equal "${ml1} "${ml2} >> /dev/null || return 1
129+
assert_equal_output_expr "FOO" "echo FOO"
130+
! assert_equal_output_expr "FOO" "echo BAR" >> /dev/null || return 1
131+
etest
132+
}

test/data/2c.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
b
2+
c

test/data/ac.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
a
2+
b
3+
c

test/data/b3.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
b
2+
c

test/data/b3ln.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2 b
2+
3 c

test/data/cd.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
c
2+
d

test/data/full.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
a
2+
b
3+
c
4+
d

test/data/tricky.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
a
2+
b
3+
c
4+
a
5+
b
6+
c

0 commit comments

Comments
 (0)