Здесь реализация аналогичной функции, но для которой позиция индекса указывается с начала строки.
Как выглядит реализация общей функции, для которой позиция индекса может быть указана с конца строки?
Пример с входными параметрами и ожидаемым результатом для требуемой функции/процедуры:
string: "2278"
string_to_insert: "."
index_position: 3
# After inserting "." to 3-rd index position from the end of "string" result should be:
expected_result = "2.278"
Связанный вопрос:
Этот вопрос является частью проекта, связанного с другим моим вопросом: Как получить текст из текущей строки в указанной позиции курсора до и после указанных граничных символов?
@F.Hauri-GiveUpGitHub Как я уже упоминал, такой подход неудобен, поскольку требует дублирования кода. И именно поэтому я создал этот и связанные с ним вопросы: показать подход без такого дублирования, с помощью функции.
cat insert_string.sh
#!/bin/bash
insert_at_position_from_end() {
local string=$1; shift
local string_to_insert=$1; shift
local index=$1; shift
declare -n out_result=$1; shift
local length=${#string}
if (( $index < 0 || $index > $length )); then
error_msg = "index out of bounds error: index=$index, string length=$length; expected indexes: [0..$length]"
out_result = ""
echo "$error_msg" >&2
return 2 # exit 2 # for intruppting the script
fi
local left_part=${string:0:$length-$index}
local right_part=${string:$length-$index:$index}
out_result = "${left_part}${string_to_insert}${right_part}"
}
capture_stderr() {
local -n stderr = "${1:?}"; shift
{ stderr = "$({ "$@" 1>&3; } 2>&1)"; } 3>&1
}
assert_equals() {
local error_message=$1; shift
local expected_result=$1; shift
local actual_result=$1; shift
if [ "$expected_result" != "$actual_result" ]; then
echo "$error_message; Expected: $expected_result; Actual: $actual_result"
fi
}
test_insert_at_position_from_end() {
# insert to the middle
local test_string = "2278"
local expected = "2.278"
local actual = ""
insert_at_position_from_end "$test_string" "." 3 actual
assert_equals "test_insert_at_position_from_end-10" "$expected" "$actual"
# insert to the beginning
local test_string = "278"
local expected = ".278"
local actual = "" # reset from previous test
insert_at_position_from_end "$test_string" "." 3 actual
assert_equals "test_insert_at_position_from_end-40" "$expected" "$actual"
# insert to the end
local test_string = "278"
local expected = "278."
local actual = ""
insert_at_position_from_end "$test_string" "." 0 actual
assert_equals "test_insert_at_position_from_end-50" "$expected" "$actual"
# try to insert out of the right border
local test_string = "278"
local actual = ""
local expected_stderr = "index out of bounds error: index=-1, string length=3; expected indexes: [0..3]"
capture_stderr actual_stderr insert_at_position_from_end "$test_string" "." -1 actual
assert_equals "test_insert_at_position_from_end-70" "$expected_stderr" "$actual_stderr"
# try to insert out of the left border
local test_string = "278"
local actual = ""
local expected_stderr = "index out of bounds error: index=4, string length=3; expected indexes: [0..3]"
capture_stderr actual_stderr insert_at_position_from_end "$test_string" "." 4 actual
assert_equals "test_insert_at_position_from_end-110" "$expected_stderr" "$actual_stderr"
}
echo "test started"
test_insert_at_position_from_end
echo "test finished"
Чтобы запустить тест:
./insert_string.sh
Отсутствие вывода с ошибками означает, что тест пройден успешно.
insert_at_position_from_end
— реализация функции, которая вставляет одну строку в другую по указанной индексной позиции с концаtest_insert_at_position_from_end
— протестируйте эту функцию. Примеры использования функции insert_at_position_from_end
можно найти здесь.stderr
для вызываемой функции.Ваша основная функция беспечно предполагает, что она была вызвана с достаточным количеством аргументов. Если вы вообще собираетесь проверять аргументы, вам обязательно следует также проверить их существование, когда это необходимо.
bash
:string = "2278"
string_to_insert = "."
index_position=3
result=${string::-index_position}$string_to_insert${string: -index_position}
echo $result
2.278
... Или даже:
if (( index_position <= ${#string} )); then
result=${string::-index_position}$string_to_insert${string: -index_position}
echo $result
else
echo "Error string '$string' too small"
fi
Дополнительную информацию об этом можно найти на странице руководства bash, в разделе «Расширение параметров» найдите «Расширение подстроки»:
man -Pless\ '+/^\ *Parameter\ *Expansion\|Substring\ *Expansion' bash
Если вы хотите, чтобы это было как функция, относительно первой ссылки, показанной в вашем запросе, которая обозначает тот же запрос, но с начала строки, а не с конца, я бы предложил просто использовать то же соглашение, что и в bash, где используется отрицательный позиция означает от конца строки, поэтому нам просто нужно написать одну функцию для обеих операций:
insertAt() {
if [[ $1 == -v ]]; then
local -n __iAt_result=$2
local __iAt_setVar=true
shift 2
else
local __iAt_result __iAt_setVar=false
fi
(( $# != 3 )) &&
printf 'Usage: %s [-v <varname>] <source string> <string to insert> <[-]position>\n' \
"${FUNCNAME[0]}" >&2 &&
return 1
case ${3#-} in *[^0-9]* )
echo 'Error: 3rd argument must be an integer!' >&2
return 1 ;;
esac
local __iAt_string=$1 __iAt_toInsert=$2 __iAt_position=$3
(( ${__iAt_position#-} > ${#__iAt_string} )) &&
echo 'Error: position out of string length' >&2 &&
return 1
printf -v __iAt_result '%s%s%s' "${__iAt_string::__iAt_position}" \
"$__iAt_toInsert" "${__iAt_string: __iAt_position}"
$__iAt_setVar || echo "$__iAt_result"
}
insertAt 314159265 . 1
3.14159265
string = "Hell!"
insertAt -v string "$string" 'o world' -1
echo "$string"
Hello world!
insertAt 314159265 1 .
Error: 3rd argument must be an integer!
Тогда, если вы попробуете:
for i in {-5..5};do
unset result
insertAt -v result 2283 . $i
[[ -v result ]] && printf '%5d: %s\n' $i "$result"
done
Ты увидишь:
Error: position out of string length
-4: .2283
-3: 2.283
-2: 22.83
-1: 228.3
0: .2283
1: 2.283
2: 22.83
3: 228.3
4: 2283.
Error: position out of string length
POSIX shell
:В POSIX sh индексация строк не определена.
Поэтому для этого нам придется найти другой путь.
POSIX shell
способ использования sed
:Используя только одну вилку, можно sed
:
insertAt() {
if [ "$1" = "-v" ]; then
__iAt_setVar=true __iAt_result=$2
shift 2
else
__iAt_setVar=false __iAt_result=__iAt_result
fi
[ $# -ne 3 ] && echo >&2 \
'Usage: insertAt [-v <varname>] <source string> <string to insert> <[-]position>' &&
return 1
case ${3#-} in
*[!0-9]*|'' )
echo 'Error: 3rd argument must be an integer!' >&2
return 1 ;;
esac
[ "${3#-}" -gt ${#1} ] &&
echo 'Error: position out of string length' >&2 &&
return 1
case $3 in
-* )
read -r "${__iAt_result?}" <<EOInLine__IAt_Result
$(echo "$1" | sed -e "s/.\{${3#-}\}$/${2}&/")
EOInLine__IAt_Result
;;
* )
read -r "${__iAt_result?}" <<EOInLine__IAt_Result
$(echo "$1" | sed -e "s/^.\{$3\}/&${2}/")
EOInLine__IAt_Result
;;
esac
if ! $__iAt_setVar; then echo "$__iAt_result"; fi
}
Будет работать точно так же:
insertAt 314159265 . 1
3.14159265
string = "Hell!"
insertAt -v string "$string" 'o world' -1
echo "$string"
Hello world!
insertAt 314159265 1 .
Error: 3rd argument must be an integer!
for i in $(seq -- -5 5); do
unset result
insertAt -v result 2283 . "$i"
[ -n "$result" ] && printf '%5d: %s\n' "$i" "$result"
done
Error: position out of string length
-4: .2283
-3: 2.283
-2: 22.83
-1: 228.3
0: .2283
1: 2.283
2: 22.83
3: 228.3
4: 2283.
Error: position out of string length
shell
без вилкиНо с петлями! Подходит для небольших струн, как указано в вашем образце.
В разделе posix оболочки мы могли бы использовать либо Удалить соответствующий шаблон префикса, либо Удалить соответствующий шаблон суффикса, в зависимости от того, какую операцию (с начала или с конца) необходимо выполнить.
Немного сжато для быстрого отображения здесь, на SO:
insertAt() {
if [ "$1" = "-v" ]; then __iAt_setVar=true __iAt_result=$2; shift 2
else __iAt_setVar=false __iAt_result=__iAt_result; fi
[ $# -ne 3 ] && echo >&2 \
'Usage: insertAt [-v <varname>] <source string> <string to insert> <[-]position>' &&
return 1
case ${3#-} in *[!0-9]*|'' )
echo 'Error: 3rd argument must be an integer!' >&2
return 1 ;; esac
[ "${3#-}" -gt ${#1} ] &&
echo 'Error: position out of string length' >&2 &&
return 1
__iAt_var1 = "" __iAt_var2 = "" __iAt_cnt=$(( ${#1} - ${3#-} ))
while [ $__iAt_cnt -gt 0 ]; do
__iAt_var1 = "?$__iAt_var1" __iAt_cnt=$((__iAt_cnt-1))
done
__iAt_cnt=$(( ${3#-} ))
while [ $__iAt_cnt -gt 0 ]; do
__iAt_var2 = "?$__iAt_var2" __iAt_cnt=$((__iAt_cnt-1))
done
case $3 in -* )
read -r "${__iAt_result?}" <<EOInLine__IAt_Result
${1%$__iAt_var2}$2${1#$__iAt_var1}
EOInLine__IAt_Result
;; * )
read -r "${__iAt_result?}" <<EOInLine__IAt_Result
${1%$__iAt_var1}$2${1#$__iAt_var2}
EOInLine__IAt_Result
;; esac
if ! $__iAt_setVar; then echo "$__iAt_result"; fi
}
Затем снова:
insertAt 314159265 . 1
3.14159265
string = "Hell!"
insertAt -v string "$string" 'o world' -1
echo "$string"
Hello world!
insertAt 314159265 1 .
Error: 3rd argument must be an integer!
for i in $(seq -- -5 5); do
unset result
insertAt -v result 2283 . "$i"
[ -n "$result" ] && printf '%5d: %s\n' "$i" "$result"
done
Error: position out of string length
-4: .2283
-3: 2.283
-2: 22.83
-1: 228.3
0: .2283
1: 2.283
2: 22.83
3: 228.3
4: 2283.
Error: position out of string length
В этом решении «$string» и «$index_position» необходимо указать дважды. Я больше предпочитаю подход с функциями для уменьшения дублирования кода. Его проще использовать и проще поддерживать.
Добавленная функция содержит: границу и другие проверки, поддерживает указание позиции от начала и до конца строки с удобным соглашением, используемым в самом bash
, имеет короткое интуитивное имя. И даже возвращаемый результат можно указать как собственное имя переменной. Это очень профессиональный подход!
Обратите внимание, что printf -v variable
является расширением по сравнению с командой POSIX printf
.
@JonathanLeffler Да, но declare -n
— это тоже башизм, и не единственный! В любом случае, я опубликовал версию, протестированную с последними версиями busybox , Dash , ksh и bash.
@AntonSamokat Рассмотрите возможность принятия ответа, если он соответствует вашему запросу!
Я буду. Но нам нужно уделить немного времени другим. На случай, если они тоже захотят поделиться своим опытом.
Почему бы не использовать local
вместо префикса __iAt_
для переменных?
Во-первых,local
— это не POSIX. Но даже в bash я использую нормализованные имена переменных, чтобы предотвратить конфликты.
Попробуйте просто:
echo ${string::-index_position}$string_to_insert${string: -index_position}