解析shell脚本中的URL

问题描述

| 我有这样的网址:
sftp://[email protected]/some/random/path
我想从该字符串中提取用户,主机和路径。任何部分都可以是随机长度。     

解决方法

        使用Python(这项工作的最佳工具,恕我直言):
#!/usr/bin/env python

import os
from urlparse import urlparse

uri = os.environ[\'NAUTILUS_SCRIPT_CURRENT_URI\']
result = urlparse(uri)
user,host = result.netloc.split(\'@\')
path = result.path
print(\'user=\',user)
print(\'host=\',host)
print(\'path=\',path)
进一步阅读:
os.environ
urlparse.urlparse()
    ,        [编辑2019] 这个答案并不是万能的,它适用于所有解决方案,旨在为基于python的版本提供一种简单的替代方案,并且最终具有比原始版本更多的功能。 它仅以重击的方式回答了基本问题,然后我自己对其进行了多次修改,以包括评论员的一整套要求。我认为,在这一点上增加更多的复杂性将使其无法维护。我知道并非所有事情都是直截了当的(例如,检查有效端口需要比较
hostport
host
),但我宁愿不增加更多复杂性。 [原始回答] 假设您的URL作为第一个参数传递给脚本:
#!/bin/bash

# extract the protocol
proto=\"$(echo $1 | grep :// | sed -e\'s,^\\(.*://\\).*,\\1,g\')\"
# remove the protocol
url=\"$(echo ${1/$proto/})\"
# extract the user (if any)
user=\"$(echo $url | grep @ | cut -d@ -f1)\"
# extract the host and port
hostport=\"$(echo ${url/$user@/} | cut -d/ -f1)\"
# by request host without port    
host=\"$(echo $hostport | sed -e \'s,:.*,g\')\"
# by request - try to extract the port
port=\"$(echo $hostport | sed -e \'s,^.*:,:,g\' -e \'s,.*:\\([0-9]*\\).*,[^0-9],g\')\"
# extract the path (if any)
path=\"$(echo $url | grep / | cut -d/ -f2-)\"

echo \"url: $url\"
echo \"  proto: $proto\"
echo \"  user: $user\"
echo \"  host: $host\"
echo \"  port: $port\"
echo \"  path: $path\"
我必须承认这不是最干净的解决方案,但它不依赖于其他脚本 像perl或python这样的语言。 (使用其中之一提供解决方案将产生更清晰的结果;)) 使用您的示例,结果是:
url: [email protected]/some/random/path
  proto: sftp://
  user: user
  host: host.net
  port:
  path: some/random/path
这也适用于没有协议/用户名或路径的URL。 在这种情况下,相应的变量将包含一个空字符串。 [编辑] 如果您的bash版本无法解决替换问题($ {1 / $ proto /}),请尝试以下操作:
#!/bin/bash

# extract the protocol
proto=\"$(echo $1 | grep :// | sed -e\'s,g\')\"

# remove the protocol -- updated
url=$(echo $1 | sed -e s,$proto,g)

# extract the user (if any)
user=\"$(echo $url | grep @ | cut -d@ -f1)\"

# extract the host and port -- updated
hostport=$(echo $url | sed -e s,$user@,g | cut -d/ -f1)

# by request host without port
host=\"$(echo $hostport | sed -e \'s,g\')\"

# extract the path (if any)
path=\"$(echo $url | grep / | cut -d/ -f2-)\"
    ,        上面的代码经过改进(添加了密码和端口解析),并在/ bin / sh中工作:
# extract the protocol
proto=\"`echo $DATABASE_URL | grep \'://\' | sed -e\'s,g\'`\"
# remove the protocol
url=`echo $DATABASE_URL | sed -e s,g`

# extract the user and password (if any)
userpass=\"`echo $url | grep @ | cut -d@ -f1`\"
pass=`echo $userpass | grep : | cut -d: -f2`
if [ -n \"$pass\" ]; then
    user=`echo $userpass | grep : | cut -d: -f1`
else
    user=$userpass
fi

# extract the host -- updated
hostport=`echo $url | sed -e s,$userpass@,g | cut -d/ -f1`
port=`echo $hostport | grep : | cut -d: -f2`
if [ -n \"$port\" ]; then
    host=`echo $hostport | grep : | cut -d: -f1`
else
    host=$hostport
fi

# extract the path (if any)
path=\"`echo $url | grep / | cut -d/ -f2-`\"
在b / c上发布,我需要它,所以我写了它(显然是基于@Shirkin \的答案),我认为其他人可能会喜欢它。     ,        此解决方案原则上在该线程中与Adam Ryczkowski的工作原理相同-但已基于RFC3986改进了正则表达式(进行了一些更改)并修复了一些错误(例如userinfo可以包含\'_ \'字符)。这也可以理解相对URI(例如提取查询或片段)。
# !/bin/bash

# Following regex is based on https://tools.ietf.org/html/rfc3986#appendix-B with
# additional sub-expressions to split authority into userinfo,host and port
#
readonly URI_REGEX=\'^(([^:/?#]+):)?(//((([^:/?#]+)@)?([^:/?#]+)(:([0-9]+))?))?(/([^?#]*))(\\?([^#]*))?(#(.*))?\'
#                    ↑↑            ↑  ↑↑↑            ↑         ↑ ↑            ↑ ↑        ↑  ↑        ↑ ↑
#                    |2 scheme     |  ||6 userinfo   7 host    | 9 port       | 11 rpath |  13 query | 15 fragment
#                    1 scheme:     |  |5 userinfo@             8 :…           10 path    12 ?…       14 #…
#                                  |  4 authority
#                                  3 //…

parse_scheme () {
    [[ \"$@\" =~ $URI_REGEX ]] && echo \"${BASH_REMATCH[2]}\"
}

parse_authority () {
    [[ \"$@\" =~ $URI_REGEX ]] && echo \"${BASH_REMATCH[4]}\"
}

parse_user () {
    [[ \"$@\" =~ $URI_REGEX ]] && echo \"${BASH_REMATCH[6]}\"
}

parse_host () {
    [[ \"$@\" =~ $URI_REGEX ]] && echo \"${BASH_REMATCH[7]}\"
}

parse_port () {
    [[ \"$@\" =~ $URI_REGEX ]] && echo \"${BASH_REMATCH[9]}\"
}

parse_path () {
    [[ \"$@\" =~ $URI_REGEX ]] && echo \"${BASH_REMATCH[10]}\"
}

parse_rpath () {
    [[ \"$@\" =~ $URI_REGEX ]] && echo \"${BASH_REMATCH[11]}\"
}

parse_query () {
    [[ \"$@\" =~ $URI_REGEX ]] && echo \"${BASH_REMATCH[13]}\"
}

parse_fragment () {
    [[ \"$@\" =~ $URI_REGEX ]] && echo \"${BASH_REMATCH[15]}\"
}
    ,        这是我的看法,大致基于一些现有的答案,但它也可以应付GitHub SSH克隆URL:
#!/bin/bash

PROJECT_URL=\"[email protected]:heremaps/here-aaa-java-sdk.git\"

# Extract the protocol (includes trailing \"://\").
PARSED_PROTO=\"$(echo $PROJECT_URL | sed -nr \'s,^(.*://).*,p\')\"

# Remove the protocol from the URL.
PARSED_URL=\"$(echo ${PROJECT_URL/$PARSED_PROTO/})\"

# Extract the user (includes trailing \"@\").
PARSED_USER=\"$(echo $PARSED_URL | sed -nr \'s,^(.*@).*,p\')\"

# Remove the user from the URL.
PARSED_URL=\"$(echo ${PARSED_URL/$PARSED_USER/})\"

# Extract the port (includes leading \":\").
PARSED_PORT=\"$(echo $PARSED_URL | sed -nr \'s,.*(:[0-9]+).*,p\')\"

# Remove the port from the URL.
PARSED_URL=\"$(echo ${PARSED_URL/$PARSED_PORT/})\"

# Extract the path (includes leading \"/\" or \":\").
PARSED_PATH=\"$(echo $PARSED_URL | sed -nr \'s,[^/:]*([/:].*),p\')\"

# Remove the path from the URL.
PARSED_HOST=\"$(echo ${PARSED_URL/$PARSED_PATH/})\"

echo \"proto: $PARSED_PROTO\"
echo \"user: $PARSED_USER\"
echo \"host: $PARSED_HOST\"
echo \"port: $PARSED_PORT\"
echo \"path: $PARSED_PATH\"
这使
proto:
user: git@
host: github.com
port:
path: :heremaps/here-aaa-java-sdk.git
for13ѭ,你得到
proto: ssh://
user: sschuberth@
host: git.eclipse.org
port: :29418
path: /jgit/jgit
    ,        如果您真的想在Shell中执行此操作,则可以通过使用awk来完成以下操作。这需要知道您实际上将传递多少个字段(例如,有时没有密码,而没有其他密码)。
#!/bin/bash

FIELDS=($(echo \"sftp://[email protected]/some/random/path\" \\
  | awk \'{split($0,arr,/[\\/\\@:]*/); for (x in arr) { print arr[x] }}\'))
proto=${FIELDS[1]}
user=${FIELDS[2]}
host=${FIELDS[3]}
path=$(echo ${FIELDS[@]:3} | sed \'s/ /\\//g\')
如果您没有awk,但是确实有grep,并且您可以要求每个字段至少包含两个字符并且在格式上可以合理地预测,那么您可以执行以下操作:
#!/bin/bash

FIELDS=($(echo \"sftp://[email protected]/some/random/path\" \\
   | grep -o \"[a-z0-9.-][a-z0-9.-]*\" | tr \'\\n\' \' \'))
proto=${FIELDS[1]}
user=${FIELDS[2]}
host=${FIELDS[3]}
path=$(echo ${FIELDS[@]:3} | sed \'s/ /\\//g\')
    ,        只是需要做同样的事情,所以很好奇是否可以单行完成,这就是我所拥有的:
#!/bin/bash

parse_url() {
  eval $(echo \"$1\" | sed -e \"s#^\\(\\(.*\\)://\\)\\?\\(\\([^:@]*\\)\\(:\\(.*\\)\\)\\?@\\)\\?\\([^/?]*\\)\\(/\\(.*\\)\\)\\?#${PREFIX:-URL_}SCHEME=\'\\2\' ${PREFIX:-URL_}USER=\'\\4\' ${PREFIX:-URL_}PASSWORD=\'\\6\' ${PREFIX:-URL_}HOST=\'\\7\' ${PREFIX:-URL_}PATH=\'\\9\'#\")
}

URL=${1:-\"http://user:[email protected]/path/somewhere\"}
PREFIX=\"URL_\" parse_url \"$URL\"
echo \"$URL_SCHEME://$URL_USER:$URL_PASSWORD@$URL_HOST/$URL_PATH\"
怎么运行的: 有一个疯狂的sed regex,可以捕获url的所有部分,而这些部分都是可选的(主机名除外) 使用这些捕获组sed输出环境变量名称及其相关部分的值(例如URL_SCHEME或URL_USER) eval执行该输出,导致这些变量导出并在脚本中可用 可以将PREFIX传递给控制输出环境变量名称 PS:将其用于任意输入时要小心,因为此代码容易受到脚本注入的影响。     ,        我做了进一步的解析,扩展了@Shirkrin给出的解决方案:
#!/bin/bash

parse_url() {
    local query1 query2 path1 path2

    # extract the protocol
    proto=\"$(echo $1 | grep :// | sed -e\'s,g\')\"

    if [[ ! -z $proto ]] ; then
            # remove the protocol
            url=\"$(echo ${1/$proto/})\"

            # extract the user (if any)
            login=\"$(echo $url | grep @ | cut -d@ -f1)\"

            # extract the host
            host=\"$(echo ${url/$login@/} | cut -d/ -f1)\"

            # by request - try to extract the port
            port=\"$(echo $host | sed -e \'s,g\')\"

            # extract the uri (if any)
            resource=\"/$(echo $url | grep / | cut -d/ -f2-)\"
    else
            url=\"\"
            login=\"\"
            host=\"\"
            port=\"\"
            resource=$1
    fi

    # extract the path (if any)
    path1=\"$(echo $resource | grep ? | cut -d? -f1 )\"
    path2=\"$(echo $resource | grep \\# | cut -d# -f1 )\"
    path=$path1
    if [[ -z $path ]] ; then path=$path2 ; fi
    if [[ -z $path ]] ; then path=$resource ; fi

    # extract the query (if any)
    query1=\"$(echo $resource | grep ? | cut -d? -f2-)\"
    query2=\"$(echo $query1 | grep \\# | cut -d\\# -f1 )\"
    query=$query2
    if [[ -z $query ]] ; then query=$query1 ; fi

    # extract the fragment (if any)
    fragment=\"$(echo $resource | grep \\# | cut -d\\# -f2 )\"

    echo \"url: $url\"
    echo \"   proto: $proto\"
    echo \"   login: $login\"
    echo \"    host: $host\"
    echo \"    port: $port\"
    echo \"resource: $resource\"
    echo \"    path: $path\"
    echo \"   query: $query\"
    echo \"fragment: $fragment\"
    echo \"\"
}

parse_url \"http://login:[email protected]:8080/one/more/dir/file.exe?a=sth&b=sth#anchor_fragment\"
parse_url \"https://example.com/one/more/dir/file.exe#anchor_fragment\"
parse_url \"http://login:[email protected]:8080/one/more/dir/file.exe#anchor_fragment\"
parse_url \"ftp://[email protected]:8080/one/more/dir/file.exe?a=sth&b=sth\"
parse_url \"/one/more/dir/file.exe\"
parse_url \"file.exe\"
parse_url \"file.exe#anchor\"
    ,        我不喜欢上面的方法,而是写了自己的方法。它用于ftp链接,如果需要,只需将
ftp
替换为
http
。 第一行是对链接的一个小验证,链接应类似于
ftp://user:[email protected]/path/to/something
if ! echo \"$url\" | grep -q \'^[[:blank:]]*ftp://[[:alnum:]]\\+:[[:alnum:]]\\+@[[:alnum:]\\.]\\+/.*[[:blank:]]*$\'; then return 1; fi

login=$(  echo \"$url\" | sed \'s|[[:blank:]]*ftp://\\([^:]\\+\\):\\([^@]\\+\\)@\\([^/]\\+\\)\\(/.*\\)[[:blank:]]*|\\1|\' )
pass=$(   echo \"$url\" | sed \'s|[[:blank:]]*ftp://\\([^:]\\+\\):\\([^@]\\+\\)@\\([^/]\\+\\)\\(/.*\\)[[:blank:]]*|\\2|\' )
host=$(   echo \"$url\" | sed \'s|[[:blank:]]*ftp://\\([^:]\\+\\):\\([^@]\\+\\)@\\([^/]\\+\\)\\(/.*\\)[[:blank:]]*|\\3|\' )
dir=$(    echo \"$url\" | sed \'s|[[:blank:]]*ftp://\\([^:]\\+\\):\\([^@]\\+\\)@\\([^/]\\+\\)\\(/.*\\)[[:blank:]]*|\\4|\' )
我的实际目标是通过url检查ftp访问。这是完整的结果:
#!/bin/bash

test_ftp_url()  # lftp may hang on some ftp problems,like no connection
    {
    local url=\"$1\"

    if ! echo \"$url\" | grep -q \'^[[:blank:]]*ftp://[[:alnum:]]\\+:[[:alnum:]]\\+@[[:alnum:]\\.]\\+/.*[[:blank:]]*$\'; then return 1; fi

    local login=$(  echo \"$url\" | sed \'s|[[:blank:]]*ftp://\\([^:]\\+\\):\\([^@]\\+\\)@\\([^/]\\+\\)\\(/.*\\)[[:blank:]]*|\\1|\' )
    local pass=$(   echo \"$url\" | sed \'s|[[:blank:]]*ftp://\\([^:]\\+\\):\\([^@]\\+\\)@\\([^/]\\+\\)\\(/.*\\)[[:blank:]]*|\\2|\' )
    local host=$(   echo \"$url\" | sed \'s|[[:blank:]]*ftp://\\([^:]\\+\\):\\([^@]\\+\\)@\\([^/]\\+\\)\\(/.*\\)[[:blank:]]*|\\3|\' )
    local dir=$(    echo \"$url\" | sed \'s|[[:blank:]]*ftp://\\([^:]\\+\\):\\([^@]\\+\\)@\\([^/]\\+\\)\\(/.*\\)[[:blank:]]*|\\4|\' )

    exec 3>&2 2>/dev/null
    exec 6<>\"/dev/tcp/$host/21\" || { exec 2>&3 3>&-; echo \'Bash network support is disabled. Skipping ftp check.\'; return 0; }

    read <&6
    if ! echo \"${REPLY//$\'\\r\'}\" | grep -q \'^220\'; then exec 2>&3  3>&- 6>&-; return 3; fi   # 220 vsFTPd 3.0.2+ (ext.1) ready...

    echo -e \"USER $login\\r\" >&6; read <&6
    if ! echo \"${REPLY//$\'\\r\'}\" | grep -q \'^331\'; then exec 2>&3  3>&- 6>&-; return 4; fi   # 331 Please specify the password.

    echo -e \"PASS $pass\\r\" >&6; read <&6
    if ! echo \"${REPLY//$\'\\r\'}\" | grep -q \'^230\'; then exec 2>&3  3>&- 6>&-; return 5; fi   # 230 Login successful.

    echo -e \"CWD $dir\\r\" >&6; read <&6
    if ! echo \"${REPLY//$\'\\r\'}\" | grep -q \'^250\'; then exec 2>&3  3>&- 6>&-; return 6; fi   # 250 Directory successfully changed.

    echo -e \"QUIT\\r\" >&6

    exec 2>&3  3>&- 6>&-
    return 0
    }

test_ftp_url \'ftp://fz223free:[email protected]/out/nsi/nsiProtocol/daily\'
echo \"$?\"
    ,        如果您可以访问Bash> = 3.0,那么也可以使用纯bash进行此操作,这要归功于重新匹配运算符
=~
pattern=\'^(([[:alnum:]]+)://)?(([[:alnum:]]+)@)?([^:^@]+)(:([[:digit:]]+))?$\'
if [[ \"http://[email protected]:3142\" =~ $pattern ]]; then
        proto=${BASH_REMATCH[2]}
        user=${BASH_REMATCH[4]}
        host=${BASH_REMATCH[5]}
        port=${BASH_REMATCH[7]}
fi
与之前的所有示例相比,它应该更快,更省资源,因为不会产生任何外部进程。     ,        如果您有权访问Node.js:
export MY_URI=sftp://[email protected]/some/random/path
node -e \"console.log(url.parse(process.env.MY_URI).user)\"
node -e \"console.log(url.parse(process.env.MY_URI).host)\"
node -e \"console.log(url.parse(process.env.MY_URI).path)\"
这将输出:
user
host.net
/some/random/path